This is my first cognito project. I am making an admin page which will have read only access to all Cognito Users and their attributes. I have looked through aws-amplify and aws-amplify-react and cannot figure out how to get them to access ListUsers function or something similar. Does anyone have an example of how this is done in React.
I am considering saving User data in a separate Dynamodb table which I can easily access. I am also wondering which is cheaper? Read/Writing User Attributes in Cognito or in Dynamodb? I can only find documentation on number of active users in Cognito and nothing on how often their attributes are read/wrote to.
I ran into a similar issue, but what I ended up doing was creating a lambda function for this and using the SDK to access this information. You could create an API endpoint through the mobilehub CLI and then the lambda it creates you can edit in your local express project under the desired get request of your choosing and put in the following:
var cognito = require('aws-sdk/clients/cognitoidentityserviceprovider');
app.get('/users', function(req, res) {
let params = {
UserPoolId: userPoolIdHere,
AttributesToGet: [
'COGNITO_ATTRIBUTE_NAME',
'COGNITO_ATTRIBUTE_NAME
];
};
cognito.listUsers(params, function(err, data) {
if(err) {
res.json(err);
} else {
res.json(data);
}
});
}
Related
I have an ASP.NET 6.0 Web API project. I would like to add authentication and authorization to it, but it must use SSO via Azure.
We already have a SPA application that does this, it uses the Angular MSAL library to redirect the user to an SSO Login page, then returns to the SPA with an access token. The access token is then added to the header of each request to the Web API, which uses it to enforce authentication.
Now we want to share our web API with other teams within our organization, and we would like to have that login process just be another API call, rather than a web page.
Conceptually, a client would hit the /login endpoint of our API, passing in a userID and password. The web API would then get an access token from Azure, then return it as the payload of the login request. It's then up to the client to add that token to subsequent request headers.
I have done this with regular ASP.NET Identity, where all of the user and role data is stored in a SQL database, but since our organization uses SSO via Azure Active Directory, we would rather use that.
I have researched this topic online, and so far all of the examples I have seen use a separate SPA, just like we already have. But as this is a web api, not a front-end, we need to have an API method that does this instead.
Is this even possible? I know Microsoft would rather not have user credentials flow through our own web server, where a dishonest programmer might store them for later misuse. I understand that. But I'm not sure there's a way around this.
Thanks.
I believe you are looking for the Resource Owner Password (ROP) flow. You can use IdentityModel.OidcClient to implement it.
Sample code:
public class Program
{
static async Task Main()
{
// call this in your /login endpoint and return the access token to the client
var response = await RequestTokenAsync("bob", "bob");
if (!response.IsError)
{
var accessToken = response.AccessToken;
Console.WriteLine(accessToken);
}
}
static async Task<TokenResponse> RequestTokenAsync(string userName, string password)
{
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync(Constants.Authority);
if (disco.IsError) throw new Exception(disco.Error);
var response = await client.RequestPasswordTokenAsync(new PasswordTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "roclient",
ClientSecret = "secret",
UserName = userName,
Password = password,
Scope = "resource1.scope1 resource2.scope1",
Parameters =
{
{ "acr_values", "tenant:custom_account_store1 foo bar quux" }
}
});
if (response.IsError) throw new Exception(response.Error);
return response;
}
}
Sample taken from IdentityServer4 repository where you can find more ROP flow client examples.
I would recommend that you don't go with this implementation and instead have all clients obtain their access tokens directly from Azure AD like you did with your Angular SPA.
I'm trying to implement App Roles in a single-tenant React + .NET Core app of ours. This app has successfully been authenticating users via MSAL, so this is just an incremental addition.
I have the app roles set up in Azure AD, and I have the Authorize attribute with role restrictions working in the .NET back-end, but for some reason I'm unable to get the roles via the react MSAL library, even though when I manually decode the token I see them in there.
I was referring to this MS sample for my code. In my index.js, I have the following:
export const msalInstance = new PublicClientApplication(msalConfig);
const accounts = msalInstance.getAllAccounts();
if (accounts.length > 0) {
msalInstance.setActiveAccount(accounts[0]);
}
Then, in the test page I have, I'm trying to access the roles array in this way (just as a test to print them out):
const TestComponent = () => {
const { instance } = useMsal();
useEffect(() => {
const activeAccount = instance.getActiveAccount();
setTokenRoles(activeAccount?.idTokenClaims?.roles);
// I've also tried:
// setTokenRoles(activeAccount?.idTokenClaims['roles']);
}, [instance]);
return (
<div>
ROLES: {JSON.stringify(tokenRoles)}
</div>
);
};
Unfortunately, tokenRoles is null. When I inspect entire idTokenClaims object, I see all the other claims, but no roles. However, I do see them in the token itself:
{
...
"roles": [
"Packages.Manage"
],
...
}
I'm really hoping to avoid manually decoding the token. There has to be a way to get it out of MSAL.
Jason Nutter's comments provided the answer, and in case this helps others I figured I'd give it a write-up.
Per the MS docs, I put the app roles on the back-end app registration. This is why I am able to have the Authorize(Roles = "Role") attribute work on the back-end. The reason I can see the roles in the access token is that the token is retrieved with the scope for that back-end API. But because I don't have those roles mirrored on the front-end app registration, I don't see anything in the id token.
There would be two options if you wanted to use the Azure app roles:
Mirror the app roles in the front-end app registration. In this way you'd have access to the roles in the id token. This sounds not good because I could foresee a typo or mismatch causing weird issues. I'm sure there might be a way using the Azure API to have a process that would sync the roles, but that's not worth it in my opinion.
Manually decode the access token on the front-end. The cleanest way I could think of to do this would be to create a roles context that would pull an access token, decode it, and store the roles for child components to refer to.
Another alternative would be to manage roles in the app itself. For us, the application in question is single-tenant, so there's not much need to do that. However, we do have a multitenant app we are moving to MSAL, and in that case we will already need to do things like validate that the tenant is authorized, and we will need more granular permissions than what this internal app needs, so we will likely have role system and have the front-end retrieve role and profile data from the back-end upon authentication through MSAL.
EDIT: What I ultimately did...
I did indeed keep the roles in the back-end only, then created a user context object that the front-end would retrieve. This user context includes the app roles, as well as other convenience data points like nickname, and is used by a React context and provider that I wrap my app in.
I have two reactjs websites, one for client user and one for admin. I have graphql api for data access. Currently, admin is using JWT and we will change to firebase authentication.
I already implement both login and registration using firebase package.
here is the create user function. So it worked and can login successfully. But my question is that how can I distinguish admin user and customer user. I saw that setCustomUserClaims is only for firebase-admin. May I know how to set the role in firebase and what is the correct way to set?
function register(name, email, password) {
return createUserWithEmailAndPassword(auth, email, password).then((res) => {
const auth = getAuth();
updateProfile(auth.currentUser, {
displayName: name
}).then(() => {
// Profile updated!
// ...
}).catch((error) => {
// An error occurred
// ...
console.log(error.message);
});
});
}
Custom claims are definitely the easiest way to add roles in Firebase Auth but you'll need to use Admin SDK in a Cloud function or any secure server environment.
I saw that setCustomUserClaims is only for firebase-admin
setCustomUserClaims() is used to set the custom claims but you can always read them using getIdTokenResult() function in client SDK.
const { claims } = await getIdTokenResult(auth.currentUser)
So if your GraphQL API runs on your own server you can install Firebase Admin there and add custom claims to Firebase auth users.
Alternatively, you can store user role in a database like Firestore or your own database and read user's role from there. One catch of store roles in a database is that you cannot access them from security rules of other services like Firebase storage if required but custom claims can be.
I have a Reactjs app (aka Single-page Application) that is uploaded in Dynamics 365 Customer Engagement (formerly CRM) as a web resource. This web resource or app is displayed in an IFRAME inside an entity form and therefore this small app already has direct access to Dynamics 365 data using the Xrm object. All good.
What I'm trying to accomplish with this app is to get it to connect to SharePoint via Microsoft Graph API and upload files and create folders.
Since the user is already signed in to Dynamics 365 and Azure AD (I guess), it is not necessary to display another popup login screen to the user.
In the msal wiki, there are 2 additional parameters that can be passed to the userAgentApplication to inform AAD that the user already signed in and they are login_hint and domain_hint. I passed these two parameters but nothing happens. Notice in the snippet below that I put logs. Only componentWillMount, before and after are logged in the console.
Not sure what is missing here.
componentWillMount() {
console.log('componentWillMount');
try {
console.log('before');
var userAgentApplication = new UserAgentApplication(applicationConfig.clientID, applicationConfig.authority, function (errorDesc, token, error, tokenType) {
// Called after loginRedirect or acquireTokenPopup
console.log('callback called');
}, {cacheLocation: 'localStorage'});
userAgentApplication.acquireTokenSilent(["user.read"], null, null, "&login_hint=first.last#mydomain.cp,&domain_hint=mydomain.com")
.then(token => console.log('token', token))
.catch((err) => console.log('err', err));
userAgentApplication.acquireTokenSilent(["user.read"], "&login_hint=first.last#mydomain.cp,&domain_hint=mydomain.com")
.then(token => console.log('token', token))
.catch((err) => console.log('err', err));
console.log('after');
}
catch (e) {
console.log('caught error: ', e);
}
}
After spending 3 days on, I will answer my own question.
There are 2 issues I found:
I need to call userAgentApplication.loginPopup and pass the user.name#domain.com to login_hint in the extraParameters, then call acquireTokenSilent. The userAgentApplication will check if there is an existing authroization.
The second issue is that acquireTokenSilent doesn't execute at all. Neither successes nor fails and there is an open issue in github.
I am building a web application using AngularJS, Firebase (SDK v3) and Google Calendar API. I'm authenticating users using Google OAuth. My purpose is to be able to create calendar events from database nodes in Firebase. So far I've managed to request access to the calendar scope with:
_authProvider = new firebase.auth.GoogleAuthProvider();
// Get permission to manage Calendar
_authProvider.addScope("https://www.googleapis.com/auth/calendar");
_fbAuthObject.signInWithRedirect(_authProvider);
I'm authenticating with the redirect flow so the authentication redirect is available as:
_fbAuthObject.getRedirectResult()
.then(function _readToken(result) {
if (result.credential) {
_googleToken = result.credential.accessToken;
var authHeader = 'Bearer '+ _googleToken;
// Just a test call to the api, returns 200 OK
$http({
method: 'GET',
headers: {
'Authorization': authHeader
},
url: 'https://www.googleapis.com/calendar/v3/users/me/calendarList/primary'
})
.then(function success(response) {
console.log('Cal response', response);
},
function error(response) {
console.log('Error', response);
});
However, it seems like outside the initial login it's not possible to get the Google access token through the Firebase SDK. It seems only possible to access the Firebase JWT token, no use with the Calendar API. I could store the access token, but this wouldn't resolve the problems when refreshing the token, etc. Is there any way to get the current Google Access token with Firebase SDK and if not, what other solutions is there to the problem without having to authenticate the user twice?
UPDATE 1:
Seems like someone else has struggled with similar problems with Facebook authentication. On that question there was a link to the Firebase documentation stating that Firebase Authentication no longer persists the access token. So how can I handle token refreshes? Is there really no answer to this?
UPDATE 2:
So, I contacted Firebase Support with a feature request about this problem and they gave me the following answer:
Thanks for taking your time to write us.
I've got your point here, this is indeed a good suggestion. We're definitely aware that many users, such as yourself, would like OAuth feature that will access token upon refresh. We're exploring potential solutions, but I cannot guarantee if this will be available anytime soon. We'll keep your feedback in consideration moving forward though. Continuous improvement is very important for our community, so thanks for bringing this up!
Keep an eye out on our release notes for any further updates.
So It seems like the access tokens are not available through the Firebase SDK at the moment. I'm still trying to find a workaround, so if anyone has ideas about a valid solution I'd be glad to hear them. And of course, I'll be posting it here if I ever find a working solution myself.
I finally got around this problem by handling the authentication outside Firebase with the Google APIs JavaScript client. This solution requires including the Google auth client as documented here. Manually handling the Firebase sign-in flow is documented here.
gapi.auth2.getAuthInstance().signIn()
.then(function _firebaseSignIn(googleUser) {
var unsubscribe = firebase.auth().onAuthStateChanged(function(firebaseUser) {
unsubscribe();
// Check if we are already signed-in Firebase with the correct user.
if (!_isUserEqual(googleUser, firebaseUser)) {
// Build Firebase credential with the Google ID token.
var credential = firebase.auth.GoogleAuthProvider.credential(
googleUser.getAuthResponse().id_token);
// Sign in with credential from the Google user.
return firebase.auth().signInWithCredential(credential)
.then(function(result) {
// other stuff...
});
The _isUserEqual function:
function _isUserEqual(googleUser, firebaseUser) {
if (firebaseUser) {
var providerData = firebaseUser.providerData;
for (var i = 0; i < providerData.length; i++) {
if (providerData[i].providerId === firebase.auth.GoogleAuthProvider.PROVIDER_ID &&
providerData[i].uid === googleUser.getBasicProfile().getId()) {
// We don't need to reauth the Firebase connection.
return true;
}
}
}
return false;
}
Now I can reference the access token like this:
var user = gapi.auth2.getAuthInstance().currentUser.get();
return user.getAuthResponse().access_token;
This still isn't the ideal solution for me, but it works for now, and I'm able to authenticate to both Firebase and the Calendar API.