New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to find the bidirectional map between Cognito identity ID and Cognito user information? #54
Comments
When the identity is created in the identity pool, can't you add 'logins' to the call? Those logins get stored in the identity pool providing a way to map between Google/Facebook/User Pool ID and the Identity pool ID. 'Logins' are optional, but they are very useful. --logins (map) Edit - you have the logins in Auth.ts/setCredentialsFromFederation. Here's an identity pool entry from my mobile app, it has the User Pool ID in it. The mobile app is based on code from Mobile Hub. jonsmirl@ubuntu-16:~$ aws cognito-identity describe-identity --identity-id us-east-1:bea3f57c-4564-47cf-b82c-a515f719d8a5 --profile bill I checked Google and FB logins and they don't display the Google/FB sub like the user pool entry does. So my mobile app is storing the User Pool ID. This seems to be missing from amplify. If that data was in the identity pool this issue would be solved. In Auth.ts there is this code: I wonder if that string is fixed format? Maybe it is legal to say... ie add in the Google sub id? That appears to be what the mobile hub code did for Cognito. |
Hi @baharev lookup user attributes by identityId is application specific. Some app may not want this for security concerns, some want to store in S3, and some prefer database. As a library we can't make those assumptions. With aws-amplify it is pretty easy to implement. Depend on your needs, maybe pick one of the below two implementation. Save private so only owner of the info can accessWhen user sign up, or maybe sign in depend on your choice,
Then do this to load user attributes,
Save public so just lookup by identityId
Then do this to retrieve user attributes,
|
@richardzcode Thanks, your first suggestion is an acceptable workaround for the time being. What I don't like about it is that the userInfo.json in your code comes from the user, and therefore cannot be trusted.
There is a misunderstanding here: I need this bidirectional map purely on the backend. The user must not be able to access it exactly for security reasons. The use cases are the followings.
Note that both use cases must happen purely on the backend, and without any interaction from the user. The true solution would be to retrieve this bidirectional map purely on the backend, without any user interaction. I think there should be an AWS API for backend code to do that. In my first comment I link to a horribly complicated "solution", but I find that unacceptably complex. |
I looked at the Cognito API reference, and it is weird too. For example:
What is going on here? Or what did I miss / misunderstand? |
@baharev In my knowledge there is no backend direct mapping between identityId and username. Backend is depend on what services provide. We can only suggest client side solution at this moment. |
The old Mobile Hub code for Android built a two way map. This Indentity pool entry was created by that code when I did a user pool login: jonsmirl@ubuntu-16:~$ aws cognito-identity describe-identity --identity-id us-east-1:bea3f57c-4564-47cf-b82c-a515f719d8a5 --profile bill Also, if you use the current hosted UI under user pool it creates a back link like above. It is only Amplify that is not creating the back link. I just used Amplify to create an entry in my identity pool corresponding to a userpool entry. The entry in the identity pool does not have the linked login info shown above. jonsmirl@ubuntu-16:~/aosp/demo/foobar/client$ aws cognito-identity describe-identity --identity-id us-east-1:f763ea29-41ce-4b86-bc58-31de68d7cce8 |
@richardzcode This map must exist on the backend, because a given user always gets the same |
@jonsmirl Sorry, I don't understand, but it seems to me that you are also struggling with this issue, or with a very similar one. |
When amplify creates an entry in identity pool corresponding to a user pool entry, why is this part missing? Other Amazon Cognito code makes this entry: "Logins": [ That entry lets you map from an identity pool ID back into a user pool ID. You can query the logins off from an identity pool entry to get that string. Then use that string to query all of the attributes for the user out of the user pool. |
@jonsmirl OK, now we are getting somewhere. Please explain:
How? Which API call will consume this mysterious |
We don't use this feature, and I see now that I was misunderstanding what I was seeing. This is the Cognito user pool id, not the sub of the user: cognito-idp.us-east-1.amazonaws.com/us-east-1_Cf6AJpe20 You would need to be able to access the token that was submitted with this identity and I don't see any obvious way to get to it. 99% of our logins are via Google/FB with only a few using User Pool. So to make this work, after you get logged in you will need a dynamodb table that maps the cognito identify id to the cognito user id. Then you will be able to find the user in the user pool. Or you will just end up doing what we did. Since the attributes on Google, FB and user pool are all different we just store a copy of the attributes we care about in our internal user database. Which is what richard said to do. This might be what you are looking for: |
@jonsmirl Thanks for the prompt reply. As I said, yes, it would be an acceptable workaround for the time being, but it requires a user login and the info comes from the user (hence cannot be trusted). What pisses me is that this map must be available in the backend, so I see no reason why the user pool owner cannot access it. It just does not make sense to me, and I am wondering why the others aren't complaining about it too.
That is the link in my very first comment. :) I know you can do it, but it is unacceptably complex. Thanks again for the prompt feedback! |
Totally agreeing with @baharev. In my use case I have stored the Cognito Identity IDs in my database and now tried to get the username of the associated userpool user. None of the APIs seems to expose this functionality. |
I was advised to retrieve the Cognito identity ID via |
However, I can't figure out when the right time is to grab this info. Upon login, when I call async signIn({ commit }, { username, password }) {
const user = await Auth.signIn(username, password);
const userInfo = await Auth.currentUserInfo();
console.log(userInfo);
authenticate(commit, user);
}, |
Ok, this works for me: async signIn({ commit }, { username, password }) {
const user = await Auth.signIn(username, password);
const credentials = await Auth.currentCredentials();
console.log('Cognito identity ID:', credentials.identityId);
authenticate(commit, user);
}, So within this code block, you could just update the user's attributes and set their Cognito identity ID in there, and you'd have immediate access to it once they're authenticated. |
@ffxsam Interesting, thanks for letting us know. I recognize If I understand correctly what you are suggesting, it is essentially the same as richardzcode's suggestion. See my previous objections why I am not happy with it as a solution. It is an acceptable workaround for the time being though. |
|
This is how I overcame the obstacle of not having the identityId on hand for my users:
Ref: inside of my DynamoDB (public db) class, my getCognitoClient method invokes the CognitoIdentityServiceProvider endpoint from the aws-sdk
Important Note You have to log into Cognito's User Pool and click Attributes and add IdentityId (as a string) to your custom attributes, for it to be populated. Hope this helps someone. Because it had me thinking I'm an idiot who should stop programming, so hopefully someone out there can reassure me to continue! |
@gbrits I think that we should get official support through the SDK, and we really should not be implementing our workarounds. Especially not in ways that involve data coming from the user (untrusted data). As far as I understand your code, it has the same issues as richardzcode's workaround. |
Totally agree. The SDK should handle these sorts of lower level operations for us. |
@wvidana I used it in the same way mentioned by @KeKs0r , directly in the put method. I only have one screen that sends to S3, I didn't see the need to do it globally But in your case, take a look using the StorageProvider |
@JesseDavda @v1pz3n Thank you for providing the go around. I am from Amplify and I am thinking of supporting this approach directly in our tool. At this moment, we are trying to understand more about the use cases to ensure we don't miss some edge cases. What you are proposing this, is the newly launched attribute-based access control (ABAC), which bridges the gap of "what we know about user"(e.g. user pool attribute) and "what the resource is identified by"(in IAM policy access control). Can you (or anyone interested in this topic) give me a little more information about the use cases that you are trying to solve? Historically, we are using And yes there is no API to get identityId from a user attribute. You can call Another potential solution is to make your cognito user pool as a developer idp, then you can use As mentioned above, one solution is to store identityId as a custom attribute, but I am not sure how that helps address the use case of "given an identity Id, I want to find the related user" easily because according to the My primary concern on using user pool attribute as identifier is the guest user use case. As you probably noticed, user pool does not support guest user, and currently we rely on identity pool to give us after reading more on the past comments so far these are the use cases I know
I am not sure if "sub" is a good replacement for identity. Given a "sub" how do you normally find the user from user pool? |
The main use case for me is simple: it does not make sense to keep track of more than one id per user. I much prefer to only use one ID everywhere per user. Having to deal with only one ID per user makes the code much clearer, neater and simpler. One reason for this preference is cost - it costs more to keep two IDs per user. Cost = more development time and complexity, more points of failure in the system, more money if your app has many users - and that's something that probably many app developers dream about. Also, developers by nature often are concerned about efficiency - so having to keep track of more than one ID per user is going to cause more developers to be more disgruntled with the inefficiencies and costs forced upon them by the library. My use case for S3 authentication is exactly as it was originally intended and described, with the three levels private / public / protected -- you have done a nice job IMO with that setup -- it's just nicer and cheaper not to have to deal with two IDs -- for me it's a simple as that. So, to answer your question, there's no major new use case for me. To answer other question "Given I keep a User model in the GraphQL schema whose primary key is "sub", as follows type User
@auth(
rules: [
{ allow: owner, ownerField: "sub" }
]
)
@key(fields: ["sub"]) {
sub: String!
...
} in other words there is not the usual Now if I need any info about the user I just retrieve it via With this setup, there is never a need to retrieve the user from the user pool -- all the info you'll ever need about the user is in the DB. |
@dorontal Thank you, and I agree with you on maintaining single source of truth. |
@hkjpotato, it would not be useful for me to also keep track of This is because - after having implemented the solution outlined by @JesseDavda as I outlined above - I do not need or use the Cognito |
@dorontal I see, do you see any issue of using Besides, do you use any AWS resource like "AWS_Gateway" and use "AWS_IAM" as Authorizer? @JesseDavda @baharev Can you help me understand more about the "no identity id" use case? Basically I am trying to see if we switch to "attribute based access control" and promote using "sub" user attribute to identify resource:
Thank you in advance! The information you provide can really help us stay on the right track. |
I have had no problems using I also tried to substitute @hkjpotato - did you follow the steps I outlined precisely as stated? I did, just now, test those steps again, and they do work for me precisely as stated. |
@hkjpotato my case is exactly the same as the @dorontal I prefer not to have to manage with multiple identities, because my system only needs to serve one visitor on the authentication page, remember password and both only use pinpoint. After it is authenticated, I use the sub in appsync, s3, dynamodb, ... I prefer that Cognito solves this and my application recognizes a single user. But in this discussion I ended up having a question, if you prefer the identityid approach, why use the sub in the datastore? |
Theoretically, |
For those using the builtin Cognito user pool username field, the mapping needs to be |
I had to recreate the userpool and this workaround stopped working. ${aws:PrincipalTag/username} Apparently the cognito team made updates to the product and we didn't know about it. |
You probably have to enable "Attributes for access control" in the identity pool again as it is tied to a specific user pool (the one you deleted)? |
@shadow-light thank you i just realized this, i forgot to put this on cdk. |
@dorontal Do we need to persist the changes to the role and to the identity pool in the cloud formation template? If not, then would that mean we need to do that manually? |
@danielkochdakitec The change to the role (in my step #1 above) was done via the console and therefore it was automatically persisted - there was no further save action required - once you make changes via the console, they remain there. The change to the identity pool (in step #2 above) was also made via the console and therefore automatically saved. |
@dorontal Thank you very much! I was just not sure what happens, when I want to deploy my amplify project to another account: Then I would need to do these changes again I guess. That's why I wanted to persist them. Regarding your note I can tell you that it still works for me. I implemented the workaround yesterday and it worked correctly. |
Thank you for confirming this workaround still works. Just tested it as well and I can attest to the same conclusion (removed the doubts from my comment above so as not to confuse). |
While it's great there's a workaround, I don't think this is acceptable long-term. Re the question of "persistence" from @danielblignaut , it's still a valid question. I'm not sure how to persist the changes to our code repository - not sure if you've had any thoughts on this @dorontal ? The latest version of amplify-cli basically adds all of the cloudformation JSON directories (amplify/backend/.../build) to gitignore. This means if we need to create a new env, or deploy to a completely different account, all of these manual hacks need to be re-applied. The quick litmus test is: perform the changes directly in the web console, and perform an |
After spending about a day trying to get the workaround working - I think I'm going to have to admit defeat and try something else. The problem for me is that - at least in the newer versions of CLI (7.5.5 as of writing) - there is no way to modify those CF templates within the Git repo. Making the changes to the JSON files in the repo has no effect. Even after manually making the edits through the console, a subsequent Thinking about this issue more holistically - I think this should be solved within the amplify tooling itself. The fact that the Amplify platform has two different notions of "User Identity" (Identity ID and Cognito User Pool sub) that are used inconsistently across the different APIs (Storage and AppSync API specifically) is a pretty fundamental bug. What makes sense to me would be to incorporate the changes above as the default behaviour for new stacks going forward, with some sort of migration path to having a unified user ID (for e.g., by adding some sort "StorageV2" API that uses the PrincipalTag in the path for protected and private files, so it matches authenticated AppSync models) |
@hkjpotato is amplify team still looking into this? |
I now believe that most likely the workaround here conflicts somehow with the sign-in process. It did not conflict with the sign in process before, at some point, but now it does. As you can see from this issue: aws-amplify/amplify-flutter#1204 -- adding attributes for access control (in this case adding the maaping It would be nice if we could still use "adding attributes for access control" so that the workaround in this issue works, but currently it does not. |
@v1pz3n Hope it does not break other things related to authentication. |
I've managed to get this working and mostly automated by making the following changes First update the auth roles to use Note: I've only updated for protected since it doesn't matter for private. I've left the code for that below but I hit a bug when enabling it where the override wouldn't work anymore and ran out of time to solve it. import { AmplifyS3ResourceTemplate } from '@aws-amplify/cli-extensibility-helper';
export function override(resources: AmplifyS3ResourceTemplate) {
resources.s3AuthProtectedPolicy.policyDocument.Statement = resources.s3AuthProtectedPolicy.policyDocument.Statement.map((statement) => ({
...statement,
Resource: JSON.parse(JSON.stringify(statement.Resource).replace('${cognito-identity.amazonaws.com:sub}', '${aws:PrincipalTag/username}')),
}));
// NOTE: We don't need these for our app and also they don't work
// resources.s3AuthPrivatePolicy.policyDocument.Statement = resources.s3AuthPrivatePolicy.policyDocument.Statement.map((statement) => ({
// ...statement,
// Resource: JSON.parse(JSON.stringify(statement.Resource).replace('${cognito-identity.amazonaws.com:sub}', '${aws:PrincipalTag/username}')),
// }));
//
// resources.s3AuthReadPolicy.policyDocument.statements = resources.s3AuthReadPolicy.policyDocument.statements.map((statement) => ({
// ...statement,
// condition: JSON.parse(JSON.stringify(statement.condition).replace(/\${cognito-identity.amazonaws.com:sub}/g, '${aws:PrincipalTag/username}')),
// }));
// console.debug(JSON.stringify(resources.s3AuthProtectedPolicy.policyDocument.Statement, null, 2));
// console.debug(JSON.stringify(resources.s3AuthPrivatePolicy.policyDocument.Statement, null, 2));
// console.debug(JSON.stringify(resources.s3AuthReadPolicy.policyDocument.statements, null, 2));
} I added a post push hook to set up the principal tag attributes, you can't do this in an override using cdk as cloudformation doesn't support it yet. aws-cloudformation/cloudformation-coverage-roadmap#779 const fixPrincipalTags = async (data, error) => {
console.log('ease');
if (error) {
console.error('Error setting up sentry release', error);
return;
}
const env = data.amplify.environment.envName;
process.env.AWS_PROFILE = `gladly-${env}`;
const client = new CognitoIdentity({ region: 'ap-southeast-2' });
const credentials = await client.config.credentials();
process.env.AWS_ACCESS_KEY_ID = credentials.accessKeyId;
process.env.AWS_SECRET_ACCESS_KEY = credentials.secretAccessKey;
process.env.AWS_SESSION_TOKEN = credentials.sessionToken;
// NOTE: Change mobile to the name of your auth in amplify
const identityPoolId = amplify.auth.mobile.output.IdentityPoolId;
const identityPool = await client.describeIdentityPool({ IdentityPoolId: identityPoolId });
const identityProviderName = (identityPool.CognitoIdentityProviders || [null])[0]?.ProviderName;
try {
const tagMap = await client.getPrincipalTagAttributeMap({ IdentityPoolId: identityPoolId, IdentityProviderName: identityProviderName });
if (tagMap?.PrincipalTags?.client === 'aud' && tagMap?.PrincipalTags?.username === 'sub') {
console.debug('No tags to fix');
return;
}
} catch (e) {
console.debug('Fixing tags');
await client.setPrincipalTagAttributeMap({
IdentityPoolId: identityPoolId,
IdentityProviderName: identityProviderName,
UseDefaults: true,
});
}
delete process.env.AWS_PROFILE;
}; This most important change and why I suspect this workaround stopped working for people is changing the trust relationship on the auth role. const addStsSessionTag = async (data, error) => {
if (error) {
console.error('Error:', error);
return;
}
const env = data.amplify.environment.envName;
process.env.AWS_PROFILE = `gladly-${env}`;
const client = new IAM({ region: 'ap-southeast-2' });
const credentials = await client.config.credentials();
process.env.AWS_ACCESS_KEY_ID = credentials.accessKeyId;
process.env.AWS_SECRET_ACCESS_KEY = credentials.secretAccessKey;
process.env.AWS_SESSION_TOKEN = credentials.sessionToken;
const roleName = amplify.providers.awscloudformation.AuthRoleName;
const role = await client.getRole({ RoleName: roleName });
const policyJSON = role.Role?.AssumeRolePolicyDocument;
if (!policyJSON) {
throw new Error(`Role ${roleName} does not have an AssumeRolePolicyDocument`);
}
const policy = JSON.parse(decodeURIComponent(policyJSON));
const actions = policy.Statement[0].Action;
if (actions.includes('sts:TagSession')) {
console.debug('Role already has sts:TagSession');
return;
}
actions.push('sts:TagSession');
console.debug('Adding sts:TagSession to policy');
await client.updateAssumeRolePolicy({ RoleName: roleName, PolicyDocument: JSON.stringify(policy) });
}; |
Ok, so after dwelling ~day and reading the same page again and again I found this:
And now it makes sense to me at least that why, JUST WHY they added examples of |
Given the Cognito identity ID, I would like to programmatically find the user name, e-mail address, etc. For example, one issue is that each user gets his/her own folder in S3 (e.g.
private/${cognito-identity.amazonaws.com:sub}/
according to themyproject_userfiles_MOBILEHUB_123456789
IAM policy) but I cannot relate that folder name (S3 prefix) to the user attributes in my user pool. The closest thing that I have found is this rather complicated code:AWS Lambda API gateway with Cognito - how to use IdentityId to access and update UserPool attributes?
Is it my best bet? Is it really this difficult?
(As a workaround, I would be happy with a post confirmation lambda trigger that creates for example a
${cognito-identity.amazonaws.com:sub}/info.txt
file in some S3 bucket, and in the info.txt file it could place the user sub from the user pool. I am not sure that this is feasible at all, it was just an idea.)The text was updated successfully, but these errors were encountered: