Are there universal Cognito Ids for AWS regardless of sign in? - amazon-web-services

I've been wrapping my head around AWS Cognito, and can't seem to find a clear answer on this one.
Here is our situation: I have a react-native mobile application (using aws-amplify and it uses three different identity providers:
AWS User Pools
Facebook
Google
I know that user pools are a method to manage, create, and sync users across platforms. Within those user pools, each user has GUID assigned to them called sub, short for subject.
I also know that if I use a third party, say Facebook, to sign in, the identity pool will get the third party user, or the user pool, temp access to the AWS services (say dynamo db).
We have another legacy database of previous info, and we want to have a matching process to assign the user to. So, if user1 logs in, they correspond to our old website with user_one in the legacy db, and we want to link up their data and save it for reference later.
My question is, in the identity pool that all 3 methods access, is there a universal GUID or ID that I can associate with each user regardless of sign in type, and record in a DB? And how do I get it using react-native?
Here is what I have tried:
Auth.signIn(username, password)
.then(user => {
// etc
User returns the user_poolId, clientId, and no identity pool info.
With a federated (FB, Google) sign in:
Auth.federatedSignIn('facebook', {
token: data.accessToken.toString(),
expires_at: data.expirationTime,
}, result)
.then(data => {
let user = {
id: data._identityId,
};
This gives me the _identityId. I'm hoping to get a value similar to this for the user pool signin.

Related

AWS Cognito Google login overwrites mapped attribute values in user pool

Question specific to SocialIDP (google signin):
I give my users the option to change their 'preferred_username'. When they do, the preferred_username attribute is updated in my cognito user pool.
However, when they sign in again (google signin), Cognito does not keep the value it has stored in the pool for 'preferred_username'. It gets overwritten by whatever Google has for that mapped attribute.
Can anyone help? Here's my function that updates the user pool attributes
export const updateCognitoUserAttributes = async (user, attributes) => {
return await Auth.updateUserAttributes(user, {
...attributes.appSpecificCode
? { 'custom:appSpecificCode': attributes.appSpecificCode}
: { 'email': attributes.email,
'family_name': attributes.family_name,
'given_name': attributes.given_name,
'preferred_username': attributes.preferred_username
}
})
.then(res => {
return res;
})
.catch(err => {
return err;
});
}
Here are 3 pics showing preferred_username before change, after change, and then after I log out and log back in, notice it's changed back to what it was before. I can see why this would happen give that it is mapped to Google's "name" attribute, but the amplify docs indicate this value is changeable (and is in fact, as shown, changed in the pool), but that change should be permanent? What's the point in allowing these attributes to be changed if they just get overwritten every time a user logs in? (it's not just preferred_username that gets overwritten. Any attribute that is mapped to a google attribute gets overwritten). Any help appreciated.
Before change
After change
After logout/login
I guess there's nothing that can be done. Just found this same issue on the amplify github. In short, from amplify documentation:
Amazon Cognito must be able to update your mapped user pool attributes when users sign in to your application. When a user signs in through an identity provider, Amazon Cognito updates the mapped attributes with the latest information from the identity provider. Amazon Cognito updates each mapped attribute, even if its current value already matches the latest information. If Amazon Cognito can't update the attribute, it throws an error. To ensure that Amazon Cognito can update the attributes, check the following requirements:
https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-specifying-attribute-mapping.html.
https://github.com/aws-amplify/amplify-js/issues/7300
sigh.
You cannot turn the overwriting off.
However, you can set up an attribute mapping of Google's preferred_username to a custom Cognito attribute like custom:preferred_username.
Then update Cognito's preferred_username using custom:preferred_username in your application code, but only if preferred_username is not set yet.
custom:preferred_username will be overwritten on each login, but after the first login you will ignore it.

Switching unauthenticated user to authenticated user

I'm trying to set my app up to allow unauthenticated users to access an AppSync API, as mentioned in https://docs.aws.amazon.com/cognito/latest/developerguide/switching-identities.html . Ideally they would be able to start using the app, then sign in and keep all their data.
I've got:
A user pool. This is set up for Google auth/regular cognito auth
An identity pool
This is linked to the user pool via a Cognito identity provider.
The authenticated/unauthenticated roles have a policy attached to them that gives them access to the GraphQL API
An AppSync API set up with AWS_IAM auth
I create the app sync client like this:
val credentialsProvider = CognitoCachingCredentialsProvider(
context,
"us-east-2:abc...etc",
Regions.US_EAST_2)
appSyncClient = AWSAppSyncClient.builder()
.context(applicationContext)
.awsConfiguration(awsConfiguration)
.credentialsProvider(credentialsProvider)
.build()
This works fine and the identity pool creates an identity for me, and I can interact with the API. Well, it creates two anonymous identity IDs, but it works. The real trouble comes when I log in:
val hostedUIOptions: HostedUIOptions = HostedUIOptions.builder()
.scopes("openid", "email", "aws.cognito.signin.user.admin")
.build()
val signInUIOptions: SignInUIOptions = SignInUIOptions.builder()
.hostedUIOptions(hostedUIOptions)
.build()
runOnUiThread {
mobileClient.showSignIn(
mainActivity,
signInUIOptions,
object : Callback<UserStateDetails?> {
override fun onResult(result: UserStateDetails?) {
Log.i("AwsAuthSignIn", "onResult: " + result?.userState)
}
override fun onError(e: Exception?) {
Log.i("AwsAuthSignIn", "onResult: " + result?.userState)
}
}
)
}
After that I see that it's created a new identity associated with the sign in, rather than use the old one. I thought it was supposed to seamlessly transfer over the old identity ID to be connected with the authenticated user.
I've also tried calling registerIdentityChangedListener to see if it fires on logging in, but it does not. It only fires when first getting the unauth identity IDs.
Also when I log into the same account from two different device it creates two different identity IDs for the same user in the user pool. Since I'm using identityId to track RDB record ownership, this means that the same user sees different items after logging in.
So is identityId the right thing to put in the database? Is it expected to be different for different devices? I'm trying to find something else to use but am coming up dry.
This is what's available in the "identity" section of the context for use with VTL resolvers:
"identity": {
"accountId": "________",
"cognitoIdentityAuthProvider": "\"cognito-idp.us-east-2.amazonaws.com/us-east-2_______\",\"cognito-idp.us-east-2.amazonaws.com/us-east-2_______:CognitoSignIn:____________\"",
"cognitoIdentityAuthType": "authenticated",
"cognitoIdentityId": "us-east-2:___",
"cognitoIdentityPoolId": "us-east-2:___",
"sourceIp": [
"_____"
],
"userArn": "arn:aws:sts::_________:assumed-role/amplify-focaltodokotlin-prod-222302-authRole/CognitoIdentityCredentials",
"username": "__________:CognitoIdentityCredentials"
}
"username" is the only other one that makes sense, but when I call AWSMobileClient.username on my side, it comes up with a different format: "Google_". So I wouldn't be able to match it up in client-side logic.
Is this possible at all or do I need to abandon the idea of unauthenticated use and go with User Pools directly?
I'll have a shot of answering this, I'll stick to rather how Cognito works than what to do with a specific SDK.
Quick recap, in Cognito, when a user authenticates, they will get 3 tokens, access, id & refresh. With an Identity Pool, they can exchange one of these (I forget which one) to get short-term credentials to assume a role. STS is what is used under the hood for this, and that's what you see in the userArn there. You don't want to look at that guy for an ID, it's an STS construct your client needs to assume an IAM role.
I'll go back to the tokens, lets look at the id_token, my favourite:
{
"at_hash": "l.......",
"sub": ".....",
"cognito:groups": [
"ap-southeast-......_Google",
"kibana"
],
"email_verified": false,
"cognito:preferred_role": "arn:aws:iam::...:role/...",
"iss": "https://cognito-idp.ap-southeast-2.amazonaws.com/ap-southeast-...",
"phone_number_verified": false,
"custom:yourapp:foo": "Bar",
"cognito:username": "Google_......",
"nonce": ".........",
"cognito:roles": [
"arn:aws:iam::.....",
"arn:aws:iam::....."
],
"aud": ".....",
"identities": [
{
"userId": "....",
"providerName": "Google",
"providerType": "Google",
"issuer": null,
"primary": "true",
"dateCreated": "1583907536164"
}
],
"token_use": "id",
"auth_time": 1596366937,
"exp": 1596370537,
"iat": 1596366937,
"email": "test#test.com"
}
I have a Google one here too, I removed a bit of stuff to hide my account etc, but anyway the id you want to use is cognito:username which will in-fact be in the form of Google_. This is internal and typically you would not show this to users. So instead in Cognito you can use the another claim preferred_username, which can also be used as an alias to sign-in as mentioned here but not for external identity providers.
You can use create custom claims to help show information on the UI, which will be prefixed with custom:, I have one here: custom:yourapp:foo. But there might be one existing for you already such as email which is available from Google. When you created your external identity provider you would have configured what claims you wanted to map from Google, email would have been there, so in your app you can read the email claim, but you should use the cognito:username in your App's backend, but keep in mind that if a user deletes and recreates their account I don't know that you get the same ID again. You may rather want users to be able to define a preferred_username on signup, which you could display in the UI, but don't use that to save data against, use the cognito:username claim.
And now for Start using the app, then sign in and keep all their data. Typically this would be implemented by saving all the data in local storage on the device, not the backend. The reason being is that if a user has not authenticated, (excluding creating a session upon opening the app), then there is no way to verify that foo#gmail.com is actually foo#gmail.com when they were hitting your API as what you saw with the unauthenticated role. I could hit your API and say that I am foo#gmail even though I was bar#gmail.
The neatest way would be to store the data locally on the device, so it wouldn't matter if the user was authenticated or not. If for some reason your app does need to store this data in the backend to function, and you cannot refactor, what you could do is use a Customized Userpool Workflow, and create a Pre-Signup Lambda, which could take a custom claim (I wouldn't use the sts userArn seems wrong but you could use that too), preferably a GUID of a shopping cart for example custom:yourapp:cartGuid. So what I mean is:
When an anonymous user visits the app for the first time, they would
be issued a GUID for a shopping cart, and save all items in that
cart.
If they choose to sign up, they can pass in a custom claim: custom:yourapp:cartGuid, and in your Lambda function you will create the user in your DB, and add the cart to their account.
Guessing another user's GUID would be near impossible, but if this is a security concern then you could create a signed token.
You probably want to clean up users's carts that don't move to the sign-up after a certain amount of time.
Just give me a comment if you have any questions or are unsure. I believe from memory that you need to use the pre-signup hook because the post-confirm doesn't have access to the claims passed in on the signup process. You may want to create the user with an unconfirmed flag in the pre-hook, and then enable them in the post-hook which I believe is more safe incase another failure would happen your pool and then you have users created in a dirty state. Best of luck I've been through Cognito battles myself and survived!

Save AWS Cognito Users in DynamoDB

I recently started experimenting with AWS AppSync but I had some questions around AWS Cognito.
I would like for users to be able to authenticate with Facebook but I need their profile picture, name and email as data for my public user profiles in my app. So far, I noticed Cognito integrates with Facebook Auth but it does not allow access to the user information and this info does not get saved in a DynamoDB table.
My question is, how can I create a new User in DynamoDB when Cognito receives a new sign in, or return an existing user/id when the user already exists in the db.
I was trying to achieve the same a few weeks ago.
After reading the docs for hours, I realised that Cognito may not help us in regards to the data that comes back from FB or how to save it.
I ended up doing the following:
(1) Using FB-SDK, pulled in the user data.
(2) Invoked a Lambda function that saved this data (like FB_id,etc) to DynamoDB.
(3) If user logged in again, their FB_id (or email) was used to check against DynamoDB entries to retrieve their data.
If Cognito is able to help us and I missed it somehow, I would love to know.
Happy Coding!
You could use custom attributes and federating user from Facebook in your user pool to achieve this. Here are the steps at high level to do this.
You will first have to define custom attributes for the profile information you want to save in each user profile.
Define attribute mapping to link the custom attributes to Facebook attributes you want to save.
Build you application using Cognito hosted pages and federation to allow your users to log in using Facebook.
After this, on each new user log in in your app a new user is created in your user pool with all the attributes that were defined in attribute mapping and values which Cognito gets in the Facebook token. Your app will get these attribute values in the IDToken issued after authentication and you app can use these.
Additionally, if you want to store these attribute values outside of Cognito user pools profile, like your own DynamoDB table, you can configure a PreSignUp trigger in the pool which will be invoked on all new user creations. You can export the user attributes from this trigger to any database of your choice.
Hope this helps.
AWS AppSync allows you to access information in the GraphQL resolver which you can choose to store in a DynamoDB table. In your case for data coming from a Facebook profile you could pass this as arguments to a GraphQL mutation or in a header to AppSync which you can then access in the resolver via $ctx.request.headers.NAME where NAME is your header name. Then you could simply choose which attributes you want to write to DynamoDB for that user as part of the mutation. More information is in the reference guide here: https://docs.aws.amazon.com/appsync/latest/devguide/resolver-context-reference.html
Since you also asked that you'd like to do a check first to see if the user is already in the DDB first you could just do an existence check first:
{
"version": "2017-02-28",
"operation": "PutItem",
"key": {
"userId": $util.dynamodb.toDynamoDBJson($ctx.identity.username),
},
"attributeValues": $util.dynamodb.toMapValuesJson($ctx.args.input),
"condition": {
"expression": "attribute_not_exists(userId)"
},
}
This checks against the username from Cognito User Pools. If you were using the Cognito Federated Identities feature it would be ctx.identity.cognitoIdentityId. If the record is already there the response that comes back will tell you which means the user is already present. You could also transform the returned message in the response mapping template by looking at $ctx.result with a conditional statement and either building the JSON response by scratch or using one of the $util.error() methods in the guide above.
Finally as you mentioned that you'll have public profile data, you might want to mark this on certain records for control. In AWS AppSync you can filter GraphQL responses on authorization metadata such as this. You would just have an attribute (aka column) on the DynamoDB record marked 'public' or 'private. Then your response template would look like so:
#if($context.result.public == 'yes')
$utils.toJson($context.result)
#else
$utils.unauthorized()
#end
You can see more examples of this here: https://docs.aws.amazon.com/appsync/latest/devguide/security-authorization-use-cases.html#public-and-private-records

Cognito User Pools - Is it possible to create a custom sign up/in form for Facebook login?

I would like to use a Cognito User Pool for Facebook logins only, which may be possible using the built in login form, but I need to use my own.
Theoretically, when it comes to a custom form, it shouldn't be hard: after I receive a user object from FB, I bind the user and email attributes to the ones in my User Pool and I save it.
But what to do about the password field and future authentication? And here I have failed during my journey...
...
userPool.signUp('FoobarUser', '**password?**', attributeList, null, function(err, result){
...
While digging deeper into the docs, I tried to implement a Identity Pool (Federated Identities), managed to save user info in the form of datasets as well, but then I realised, querying these sets gonna be a huge pain if possible at all.
Maybe I'm failing to understand the concepts, I would be really thankful if someone could suggest a way to manage facebook logins in a nicely organised fashion using Cognito.
So here is what I understand from your query.
Setup
Link Facebook to userpool
The app client allows only Facebook login, no other providers allowed (not even Userpool)
On login, you want Facebook user's info to be automatically populated in your Userpool
You don't want to use Cognito's builtin UI but use your own
My 2 Cents
In your app client, just select Facebook
In your UI, have a login button. On clicking it should redirect to your userpool's authorization endpoint
https://your_domain.auth.us-east-1.amazoncognito.com/oauth2/authorize?redirect_uri=https://www.example.com&response_type=token&client_id=your_appclient_id
If you want to use your own UI with multiple providers, allow the same in Client and on clicking the appropriate button in your UI (say Facebook), redirect users to the authorize endpoint but append the identity_provider in the URL
https://your_domain.auth.us-east-1.amazoncognito.com/oauth2/authorize?redirect_uri=https://www.example.com&response_type=token&client_id=your_appclient_id&identity_provider=Facebook
If you want to see the names of all supported identity providers, use ListIdentityProviders API call
This way, all Facebook users will be automatically created in your userpool. Of course, their names will be random like Facebook_123jkjdwj but all their details will be correctly populated from the token as per your attribute mapping. As a plus, all auto-created users from a particular provider are added to an auto-created group 'Userpoolid_providername' eg. us_east_1_xxxx_Facebook.

How to link developer authenticated user across devices in Cognito

I'm just trying to get a handle over this framework and I want to confirm that my approach is correct.
I can authenticate my own user using the method: getOpenIdTokenForDeveloperIdentity and supplying an IdentityPoolId and a Logins key-pair with my Developer provider name and a token which I provide.
Now, from what I understand, when the user logs into a second device, in order for Cognito to understand that this is the same user, I have to provide it with an IdentityId. However, I'm not sure of the best way to get the IdentityId programmatically so that it will match up with the initial login.
The only technique I can think of is to store the IdentityId in my own DB and provide my own method for retrieving it. Is this the best way? Or should I be working with this framework differently?
I'm still a beginner to AWS in general and I'm just trying to understand the best practices for this framework.
BTW, I'm implementing the Android SDK and the PHP SDK for my backend.
When you use getOpenIdTokenForDeveloperIdentity, it returns the identity id associated with the user identifier you provided. So if the user identifier you use is the users' username, when you call getOpenIdTokenForDeveloperIdentity with that same username from the second device, it will return the associated identity id. There is no need to store the identity id unless you want to, it is provided to you each time you call getOpenIdTokenForDeveloperIdentity.
This blog post may be of further help:
http://mobile.awsblog.com/post/Tx2FL1QAPDE0UAH/Understanding-Amazon-Cognito-Authentication-Part-2-Developer-Authenticated-Ident
When you use Cognito, your user first authenticates with an Identity Provider (such as facebook, google or other Oauth provider), and the token you get back from it is sent to Cognito and is the key to tying your users information together across logins on other devices.
You don't need to store this information in a database, unless you are writing you own custom identity provider and not using one of the public ones available.