Mapping DynamoDB Item to Cognito User Pool Object - amazon-dynamodb

I am doing research on Cognito User Pool, Federated Identities and DynamoDB. In order to achieve fine grained access control, I have to use Cognito Identity Id in my DynamoDB table because anything in user pool cannot be referred in IAM Policy. It is worth mentioning that ${cognito-identity.amazonaws.com:sub} refers to identity id and not the “sub” which we see in Cognito user pool.
The question is that how will I map the item in DynamoDB to the user contact?
For example, consider a scenario, if a user of my application has a pending payment and if I have to contact him regarding payment. Or if a user has reported a problem and I have to lookup his data to do a typical customer support.

As you can already get the app user's data from your database with her unique Cognito identity id, all that's left is to get the user's email address. You can do this in a couple of ways. I'll use Android as an example.
You could parse the JWT id token for the email attribute.See this Stack Overflow answer for details:
Parse JWT token payload data to get certain value in android/java
Alternatively, you could do it this way:
CognitoUserPool userPool = new CognitoUserPool(context, userPoolId, clientId, clientSecret);
CognitoUser user = userPool.getUser(userId);
GetDetailsHandler handler = new GetDetailsHandler() {
#Override
public void onSuccess(final CognitoUserDetails list) {
// Successfully retrieved user details
Map mDetails = list.getAttributes().getAttributes();
String email = mDetails.get("email").toString(); // your user email address
// do stuff with the email address
}
};
user.getDetails(handler);
From the AWS docs:
https://docs.aws.amazon.com/cognito/latest/developerguide/using-amazon-cognito-user-identity-pools-android-sdk.html

Related

Firebase auth overriding user providers (email + password, phone) when signing-in with Google

As we have seen in another posts related to this situation and github issues, the expected behavior is that Google IDP overrides other non trusted providers related to the same email as an example, another account with the same email + password (non-verified).
Trying to understand Firebase Authentication one account per email address and trusted providers
Firebase Overwrites Signin with Google Account
https://github.com/firebase/firebase-ios-sdk/issues/5344
https://groups.google.com/g/firebase-talk/c/ms_NVQem_Cw/m/8g7BFk1IAAAJ
So, ok, according to google that's the expected behavior.
Our questions comes when we go to the documentation and there's an example of a user login in with google and getting this error auth/account-exists-with-different-credential just because there's another account created with email+password with the same email. Then, they recommend to catch the error, check the user email related login methods and ask the user to login with the other provider and then link to google.
Does this make sense ? If they say the expected behavior is that google as a trusted provider will override the others (this is what happens to us) how is possible that the case of the code example would even occur ?
https://firebase.google.com/docs/auth/web/google-signin#expandable-1
// Step 1.
// User tries to sign in to Google.
auth.signInWithPopup(new firebase.auth.GoogleAuthProvider()).catch(function(error) {
// An error happened.
if (error.code === 'auth/account-exists-with-different-credential') {
// Step 2.
// User's email already exists.
// The pending Google credential.
var pendingCred = error.credential;
// The provider account's email address.
var email = error.email;
// Get sign-in methods for this email.
auth.fetchSignInMethodsForEmail(email).then(function(methods) {
// Step 3.
// If the user has several sign-in methods,
// the first method in the list will be the "recommended" method to use.
if (methods[0] === 'password') {
// Asks the user their password.
// In real scenario, you should handle this asynchronously.
var password = promptUserForPassword(); // TODO: implement promptUserForPassword.
auth.signInWithEmailAndPassword(email, password).then(function(result) {
// Step 4a.
return result.user.linkWithCredential(pendingCred);
}).then(function() {
// Google account successfully linked to the existing Firebase user.
goToApp();
});
return;
}
// All the other cases are external providers.
// Construct provider object for that provider.
// TODO: implement getProviderForProviderId.
var provider = getProviderForProviderId(methods[0]);
// At this point, you should let the user know that they already have an account
// but with a different provider, and let them validate the fact they want to
// sign in with this provider.
// Sign in to provider. Note: browsers usually block popup triggered asynchronously,
// so in real scenario you should ask the user to click on a "continue" button
// that will trigger the signInWithPopup.
auth.signInWithPopup(provider).then(function(result) {
// Remember that the user may have signed in with an account that has a different email
// address than the first one. This can happen as Firebase doesn't control the provider's
// sign in flow and the user is free to login using whichever account they own.
// Step 4b.
// Link to Google credential.
// As we have access to the pending credential, we can directly call the link method.
result.user.linkAndRetrieveDataWithCredential(pendingCred).then(function(usercred) {
// Google account successfully linked to the existing Firebase user.
goToApp();
});
});
});
}
});
There's another example with the same structure in the flutter docs:
https://firebase.google.com/docs/auth/flutter/errors#handling_account-exists-with-different-credential_errors
Is this a contradiction in the documentation ? Again, if Firebase will always give priority to the trusted IDP (Google email) in this case, how is it possible to get this error if the other provider will be deleted (at least when having account linking activated - single account per email activated)
At least this is our case. We create an account with email & password and then try to login with google with the same email and what happens is that the email&password account is overwritten by the new google provider.
Unfortunately, you can't change it. If a user with #gmail.com email and password authentication updates their profile picture and then later logins with Google then the profile picture and any other information will be overwritten with the data from Google. The only option is to create a user record in the database that gets populated with the user data (displayName, photoURL etc) when the user is created for the first time. You then always use the data from this record instead of the default user object that is returned by the authentication.
The other advantage of creating a record is that you can attach a listener to it. That way if the user changes their details then it gets reflected everywhere.

Flutter Firebase authentication: reverting back to a specific anonymous user

The Firebase authentication documentation for Kotlin states that:
The call to linkWithCredential will fail if the credentials are already linked to another user account. In this situation, you must handle merging the accounts and associated data as appropriate for your app:
And then a code example is provided, the Flutter equivalent of which could be written as follows:
final prevUser = FirebaseAuth.instance.currentUser;
try {
final result = await FirebaseAuth.instance.signInWithCredential(credential);
final currentUser = result.user;
// Merge prevUser and currentUser accounts and data
// ...
} catch (e) {
// ...
}
Let's assume prevUser is an anonymous user. If the merge of prevUser and currentUser accounts and data doesn't succeed, is there any way to restore the Firebase sign-in state to what it was prior to the call to signInWithCredential, i.e. to make prevUser the currently signed in Firebase user again?
My app backend has its own representation of a user - AppUser - that is keyed by Firebase user uid, and there is an AppUser user that corresponds to (i.e. has the same uid as) prevUser. I don't want to recover from the failed merge by signing in anonymously, because that would give me a new anonymous user, with a different uid, which doesn't match up with said AppUser user.
No. Once an anonymous user signs out, or becomes replaced with a new sign-in, that anonymous account is effectively lost forever, from the perspective of the client app. The client app was the only holder of credentials that could perform a sign-in on that account.
You might want to consider at least persisting the uid of that account, then provide it to whatever process performs the merge. This would typically be done on a backend so that it could bypass any security restrictions on what can be done with an account when the user is not controlling it directly from a client app.

Is there any way to get firebase Auth UID without login?

What i want to know is without login get Auth User UID from firebase
that already registered user
I already read firebase document but all the explain focus on when user logged only at that time can get UID...
but i just want to get UID and user email address without login situation
Is there any way?
Nope, there is no way using firebase authentication to retrieve the uid or email is the user is currently not logged in.
The only other way to actually retrieve them, is to use firebase database. So, when the user registers in your application, you also send the data of the user to the database and then you will be able to retrieve the data later on even if he is not logged in.
For example (on register) you can send this data to the database:
users
userId
email : email_here
name : name_here
Your question is not clear.
You can create the user via the Admin SDK if you know the user's credentials. You can also lookup an existing user's information by email or uid with the Admin SDK.
Learn more about this from the official docs.
You can apply a trick to do this. Though this is not an optimal solution.
One thing you have to remember that, "There is no way to get a UID without login".
Create a dummy user from your backend with a dummy email and password.
Send the email and password from the backend to your client-side app. You can always hash the password if you want to give an extra security layer.
Now call signInWithEmailAndPassword function from firebase SDK and provide email and password which came from the backend.
The user is already registered in the backend, so now you can get the UID from your client app.
mAuth.signInAnonymously()
.addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
if (task.isSuccessful()) {
// Sign in success, update UI with the signed-in user's information
Log.d(TAG, "signInAnonymously:success");
FirebaseUser user = mAuth.getCurrentUser();
Log.d("userstatus","user id is "+user.getUid());
} else {
// If sign in fails, display a message to the user.
Log.w(TAG, "signInAnonymously:failure", task.getException());
}
// ...
}
});

Google Apps Super Admin access to domain user calendars

I have a large number of resource calendars that my users need to have different degrees of access to depending on their roles. I am writing an application to manage those Acl rules, but would like to also manage which calendars appear in end users calendar feeds (i.e. I don't want to have them suddenly see all 130 resource calendars and have to filter through them)
I need to use the CalendarList api access, but when I pass users//calendarList instead of users/me/calendarList authorized with Super Admin credentials, the service returns a 404 response.
My goal is to iterate through my list of shared calendar resources and for-each user who is a reader or freeBusyReader, set their calendarListEntry.hidden to true.
So, I discovered the answer. In order to impersonate a user, you must set the "User" to the email address you wish to impersonate when initializing the service account credential:
public static ServiceAccountCredential Credential(string[] Scopes, string impersonateEmail = null)
{
ServiceAccountCredential cred = ServiceAccountCredential.FromServiceAccountData(credential_stream);
var initializer = new ServiceAccountCredential.Initializer(cred.Id)
{
User = string.IsNullOrEmpty(impersonateEmail)?service_acct:impersonateEmail,
Key = cred.Key,
Scopes = Scopes
};
return new ServiceAccountCredential(initializer);
}

Why add claims in OAuthAuthorizationServerProvider.GrantRefreshToken?

I'm configuring AspNet.Identity for OAuth 2 with bearer tokens and I have seen multiple examples of implementing the OAuthAuthorizationServerProvider.GrantRefreshToken method, where the author demonstrates the ability to add a claim to the new ClaimsIdentity as seen below.
I am trying to understand this in the context of my single-server (i.e. my Web API project is both Authorization + Resource server) which I may split into separate servers at a later date, if necessary.
public override Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
{
var originalClient = context.Ticket.Properties.Dictionary["as:client_id"];
var currentClient = context.ClientId;
if (originalClient != currentClient)
{
context.SetError("invalid_clientId", "Refresh token is issued to a different clientId.");
return Task.FromResult<object>(null);
}
// Change auth ticket for refresh token requests
var newIdentity = new ClaimsIdentity(context.Ticket.Identity);
// CONSIDER: I don't know why you would add a claim here, but here's an example.
//var newClaim = newIdentity.Claims.Where(c => c.Type == "newClaim").FirstOrDefault();
//if (newClaim != null)
//{
// newIdentity.RemoveClaim(newClaim);
//}
//newIdentity.AddClaim(new Claim("newClaim", "newValue"));
var newTicket = new AuthenticationTicket(newIdentity, context.Ticket.Properties);
context.Validated(newTicket);
return Task.FromResult<object>(null);
}
The documentation states:
"The application must call context.Validated to instruct the Authorization Server middleware to issue an access token based on those claims and properties."
I don't understand this. I thought we were handing out a refresh token, not an access token.
Also, "The call to context.Validated may be given a different AuthenticationTicket or ClaimsIdentity in order to control which information flows from the refresh token to the access token."
I thought all of the claims were stored in my signed and encrypted access token, which is passed as Authorization: Bearer XXXXXX. However, I have a tenuous grasp on how ClaimsIdentity and AuthenticationTicket actually relate to anything in my OAuth 2.0 flow.
My best guess is that GrantRefreshToken needs to take the already authenticated and authorized identity (context.Ticket.Identity) and validate that a refresh token should be added to it by calling context.Validated.
Refresh Tokens in OAuth2 are just another type of grant: an alternative method of obtaining a new access_token.
For this reason the RefreshToken must be validated in order to emit a new access_token for the user.
When persisting the refresh token to the underlying storage (e.g. a database) the entire user identity must be stored along with it (usually using SerializeTicket() method of the AuthenticationTokenCreateContext object). This means that any change in the claims acquired during the first access_token generation will not be propagated to other access_tokens emissions using the RefreshToken grant by default (you will need to reload again those claims if you need to update them in the access_token).
I believe this is the main reason why many examples shows how to add/substitute claims in the new Identity inside the GrantRefreshToken method.
I will try to further clarify what usually happens when supporting the RefreshTokenGrant:
The user authenticates himself using any kind of supported grant (e.g. ResourceOwnerCredentials);
We create an Identity bounded to this specific user (we can add Claims at this point) and use it to create a new AuthenticationTicket (that we validate by calling Validated(ticket) on the specific context object) which will be used to create the access_token;
The framework generates a new refresh token calling CreateAsync on the provided IAuthenticationTokenProvider. Inside this method we have to retrieve the ticket and store it into some kind of persistence storage (e.g. a DB) along with an unique Id and some useful metadata. This Id is the refresh_token for the user point of view.
We return to the user the access_token (which contains the serialized claims for the user) and the refresh_token (which is just a reference).
After some time the user must authenticate again (e.g. the access_token expired), so he will send a request to the Token endpoint using the refresh_token.
We retrieve the refresh_token record from the persistent storage and deserialize the ticket that will be used to create a new Identity. This ticket contains all Claims that we added on the first authentication (this is almost an exact copy of the first access_token): if any of those Claims changed during the interval between the current moment and the first authentication (e.g. a new Role is added to the user, email has changed, etc.) we have now the chance to modify the new Identity and add/substitute those Claims, so that the new access_token will reflect the changes.
The flows continues by validating the ticket (as before) and generating a pair of access_token and refresh_token to send to the user.

Resources