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 the myproject_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:
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.)
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)
A set of optional name-value pairs that map provider names to provider tokens.
Edit - you have the logins in Auth.ts/setCredentialsFromFederation.
Why aren't these getting stored in the identity pool along with the ID from federation, I'm sure that data used to be in my identity pools, so where did it go?
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
{
"Logins": [
"cognito-idp.us-east-1.amazonaws.com/us-east-1_Cf6AJpe20"
],
"LastModifiedDate": 1512005509.277,
"CreationDate": 1512005509.237,
"IdentityId": "us-east-1:bea3f57c-4564-47cf-b82c-a515f719d8a5"
}
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:
private setCredentialsFromFederation(provider, token, user) {
const domains = {
'google': 'accounts.google.com',
'facebook': 'graph.facebook.com',
'amazon': 'www.amazon.com'
};
I wonder if that string is fixed format? Maybe it is legal to say...
'google': 'accounts.google.com/34455444444',
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.
When user sign up, or maybe sign in depend on your choice,
Auth.currentUserInfo()
.then(info => {
Storage.put(
'userInfo.json',
JSON.stringify(info),
{ level: 'private', contentType: 'application/json' }
);
});
Then do this to load user attributes,
Storage.get('userInfo.json', { level: 'private', download: true})
.then(data => console.log('info...', data.Body.toString()));
Auth.currentUserInfo()
.then(info => {
Storage.put(
identityId + '_userInfo.json',
JSON.stringify(info),
{ level: 'public', contentType: 'application/json' }
);
});
Then do this to retrieve user attributes,
Storage.get(identityId + '_userInfo.json', { level: 'public', download: true})
.then(data => console.log('info...', data.Body.toString()));
@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.
lookup user attributes by identityId is application specific.
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
{
"Logins": [
"cognito-idp.us-east-1.amazonaws.com/us-east-1_Cf6AJpe20"
],
"LastModifiedDate": 1512005509.277,
"CreationDate": 1512005509.237,
"IdentityId": "us-east-1:bea3f57c-4564-47cf-b82c-a515f719d8a5"
}
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
{
"LastModifiedDate": 1517513923.798,
"CreationDate": 1517513923.798,
"IdentityId": "us-east-1:f763ea29-41ce-4b86-bc58-31de68d7cce8"
}
@richardzcode This map must exist on the backend, because a given user always gets the same ${cognito-identity.amazonaws.com:sub}
. It must be solvable purely on the backend.
@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": [
"cognito-idp.us-east-1.amazonaws.com/us-east-1_Cf6AJpe20"
],
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:
Then use that string to query all of the attributes for the user out of the user pool.
How? Which API call will consume this mysterious "cognito-idp.us-east-1.amazonaws.com/us-east-1_Cf6AJpe20"
and give me the user attributes?
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
I was thinking that last part was the sub of the user in the pool and it is not.
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.
This might be what you are looking for:
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 Auth.currentUserInfo()
and store it as an attribute in the user object in the user pool.
However, I can't figure out when the right time is to grab this info. Upon login, when I call Auth.currentUserInfo()
, the id
property is undefined.
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 Auth.signIn()
and Auth.currentCredentials()
but could you explain what authenticate
is in your code?
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.
authenticate
isn't relevant here, just my own function that commits data to a Vuex store.
This is how I overcame the obstacle of not having the identityId on hand for my users:
login() {
let loading = this.loadingCtrl.create({
content: 'Please wait...'
});
loading.present();
let details = this.loginDetails;
Auth.signIn(details.username, details.password)
.then(user => {
Auth.currentCredentials().then(credentials => {
var identityId = credentials.identityId;
let params = {
"AccessToken": user.signInUserSession.accessToken.jwtToken,
"UserAttributes": [
{
"Name": "custom:identityId",
"Value": identityId
}
]
}
// Save the identityId custom attribute to the user
this.db.getCognitoClient()
.then((client) => {
client.updateUserAttributes(params, function(err, data) {
if (err) console.log(err, err.stack);// an error occurred
else {
console.log(data);
}
});
});
});
logger.debug('signed in user', user);
if (user.challengeName === 'SMS_MFA') {
this.navCtrl.push(ConfirmSignInPage, { 'user': user });
} else {
this.navCtrl.setRoot(TabsPage);
}
})
.catch(err => {
logger.debug('errrror', err);
this.error = err;
})
.then(() => loading.dismiss());
}
Ref: inside of my DynamoDB (public db) class, my getCognitoClient method invokes the CognitoIdentityServiceProvider endpoint from the aws-sdk
getCognitoClient() {
return Auth.currentCredentials()
.then(credentials => new AWS.CognitoIdentityServiceProvider({ credentials: credentials }))
.catch(err => logger.debug('error getting document client', err));
}
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.
I agree this is extremely complicated. Why can't the Cognito user attributes just be passed in through the request. Even digging out the sub is a pain in the rear.
Accessing this attribute:
req.apiGateway.event.requestContext.identity.cognitoAuthenticationProvider
yields this as a result
cognito-idp.us-east-1.amazonaws.com/us-east-1_blah,cognito-idp.us-east-1.amazonaws.com/us-east-1_blah:CognitoSignIn:XXXXXXX-XXXXX-XXXX-XXX-XXXXXX
Really? I have to parse that to get the 'subject'? I would really like the username and email address but in order to do that I have to change the authorizer on the api gateway to use Cognito instead of IAM and then somehow get the API class methods to provide the token in such a way that it can be used. This is crazy!
Hi @keithdmoore there are a number of ways you can simplify this, but we are also looking into how we can do this on both the library and service side. Until then, the api gateway body mapping templates can help you pass/organize things on the lambda context, and as far as the sub, this is available in the jwt token, there is a pretty indepth blog post on some of this here:
https://aws.amazon.com/blogs/compute/secure-api-access-with-amazon-cognito-federated-identities-amazon-cognito-user-pools-and-amazon-api-gateway/
Also, on the client side, you can retrieve the sub from Amplify Auth with:
let session = await Auth.currentSession()
// session will be a json obj with your tokens
{
"idToken": {
"jwtToken": "...",
"payload": {
"sub": "...", // <-- here is your subject
"email_verified": false,
"iss": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_xxx",
"phone_number_verified": true,
"cognito:username": "uname",
"aud": "...",
"event_id": "...",
"token_use": "id",
"auth_time": 1525294669,
"phone_number": "...",
"exp": 1525551200,
"iat": 1525547600,
"email": "..."
},
// as well as other tokens and payloads
"refreshToken": {}, "accessToken": {}
}
}
@mlabieniec Thanks for the info. However, I am using a LAMBDA_PROXY to route to Express so I don't think I can modify the request body mappings. Also, passing those parameters from the client is not secure. What I really wanted to do was to retrieve dynamoDB entries for the currently logged in user's username. As a workaround, I guess I will just use the sub
instead and parse it out of the the provider attribute and run my dynamoDB query using the DocumentClient.
I also struggled with this weirdly lacking feature (seems so basic ha?)
My eventual solution is to use a DynamoDB table in order to store user information (but not password, of course). I had a feeling i shouldn’t trust Cognito to easily retrieve that information upon request.. apparently I was right. Since those attributes cannot change (I.e. phone number), there׳s no issue with syncing. However, I chose to create a trigger on Cognito that will update db upon use signup to reduce risk of problems.
The more important part is to use COGNITO_USER_POOLS as my authorizer on apig. All you need is to pass along a token in the headers, and you’ll have the user name available in Lambda. From there you can lookup the db and voila.
What brings me here is the fact that the Amplify documentation for storage gives instructions for accessing other users' files by means of the identityId but does not provide the means for obtaining said identityId or linking it to a userpool identity. The documentation says the following:
To get other users’ objects
Storage.list('photos/', { level: 'protected', identityId: 'xxxxxxx' // the identityId of that user }) .then(result => console.log(result)) .catch(err => console.log(err));
Guys confirmed from Cognito service team that unfortunately this feature is not supported.
@powerful23 OK, where or how I can submit a feature request?
People need this feature.
I will create a feature request on your behalf. @baharev
@powerful23 I understand that there is nothing the Amplify team can do about it, but please tell me how I can ask for this feature from the Cognito team.
@yuntuowang Perfect, thank you!
This identity ID is also referred for getting other users’ objects in the docs here like:
Storage.get('test.txt', {
level: 'protected',
identityId: 'xxxxxxx' // the identityId of that user
})
.then(result => console.log(result))
.catch(err => console.log(err));
I tried to get that the identityId of that user via a post confirmation lambda trigger to store in DynamoDB, but no chance.
Even if it was returned by Storage
API after a put, it would make everything easier. But it is not available neither.
Hi @yuntuowang, is there a feature request for this publicly accessible that we can track?
@yuntuowang the above is a key flaw which essentially makes S3 Storage nearly unusable for anyone with proper application structures; unless you do some dirty work-arounds.
The cognito event trigger on new user registration does not supply the IdentityId (so it cannot be placed in say DynamoDB for access) and the only way to obtain the IdentityId is when a user is logged in having to re-submit a separate request to update the information.
@yuntuowang Any news on the progress?
@yuntuowang any updates?
Any updates???
I have this problem also and as @baharev has said before:
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.
Totally identified with that!
Having this issue too. Everything about the possible workarounds has been said already so I'll just leave it at that.
@yuntuowang This issue the second most commented open issue, with 19 participants. Could you give it a higher priority, please?
We don't comment on the status or priority of feature requests. I'll suggest one more approach which should address the security concerns expressed above.
Since the attribute cannot be set by the end user and there are no assumptions about what the end user provides the Lambda this should be secure.
That seems kind of excessive. Why not just update the user's attributes in the user pool all in the client side, without the Lambda calls?
const user = await Auth.signIn(username, password);
const credentials = await Auth.currentCredentials();
await Auth.updateUserAttributes(user, {
'custom:identity_id': credentials.identityId,
});
@behrooziAWS Thanks, but it is still a workaround at best. Far from simple, requires the user to login, I have to set up yet another service (Lambda), implement the workaround, test it, etc.
@behrooziAWS I am not sure your approach is safe: I think you do assume that the user will pass in his/her own id token. As a malicious user, I could create two users, and pass in the valid id token of the other user, messing things up on the backend. I do not see how your workaround would prevent this.
@baharev If the malicious user passes a valid id token for the other user, because we are getting the identity by using the token they passed in, and the username from the token they passed in, it will just set the identity id for the other user correctly in the other users' profile.
This dichotomy between user pool and identity pool accounts for the most confusing part of the aws-api-gateway-developer-portal codebase.
export function init() {
// attempt to refresh credentials from active session
userPool = new CognitoUserPool(poolData)
cognitoUser = userPool.getCurrentUser()
if (cognitoUser !== null) {
cognitoUser.getSession(function(err, session) {
if (err) {
logout()
console.error(err)
return
}
const cognitoLoginKey = getCognitoLoginKey()
const Logins = {}
Logins[cognitoLoginKey] = session.getIdToken().getJwtToken()
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: cognitoIdentityPoolId,
Logins: Logins
})
AWS.config.credentials.refresh((error) => {
if (error) {
logout()
console.error(error)
} else {
initApiGatewayClient(AWS.config.credentials)
updateAllUserData()
}
})
})
} else {
initApiGatewayClient()
}
}
Wot?
I've for now recommended my client to stay away from a userpool + identitypool solution, and to have a userpool only.
@behrooziAWS does that seem like a good workaround for this problem? Since I don't need federated web identities, and I don't need the single-page-app client to ever assume an iam role using the identity pool mechanism, that seems OK.
@lestephane Interesting. If your client app never has to assume an IAM role then why are you using Cognito at all? The sole reason why I am using Cognito is that my client app has to assume an IAM role.
I've for now recommended my client to stay away from a userpool + identitypool solution, and to have a userpool only.
I wonder how you get away without the identitypool if you need IAM role for the client.
@behrooziAWS Maybe it is "safe" but I still don't like this workaround. It somehow reminds me to Meltdown and Spectre. And as I mentioned, this workaround is unnecessarily complicated.
@baharev I use this because a user registration and login system is not my core competency. Why would I contemplate implementing one of my own? My client app invokes an endpoint that verifies the JWT token issued by Cognito. And then, based on that, it returns an apiKey, which the client can use inside of an x-api-key header. It means i don't overly rely on AWS's sdk, and keep things simple. Is there any flaw in my logic? I'm happy to stand corrected...
@lestephane There is some misunderstanding: I would like to understand your use case. That is all. I did not say that your approach was wrong.
If I understand correctly: If you do not use the identity pool part of Cognito, you can only check whether the user is logged in or not. And apparently that is enough for your use case. (?)
It is not clear to me how you make sure that the user can save his/her files on S3, and only he/she can access them. If your use case involves something like that, you must be re-implemeting something that IAM would give you practically for free. Or I am missing something obvious here...
In any case, I would like to know more about your use case. Thanks!
If I understand correctly: If you do not use the identity pool part of Cognito, you can only check whether the user is logged in or not. And apparently that is enough for your use case. (?)
Correct. I don't understand what identity pools would bring me (yet). And translating one id from user pool subject id to identity pool id is apparently nigh impossible.
It is not clear to me how you make sure that the user can save his/her files on S3, and only he/she can access them
There is no need for us (yet) to invoke any aws api from the client single page app using an assumed role. We just want to offload the user registration, login, storage of usernames / emails and passwords to aws. Not our core business. We have a developer api portal (see the aws-api-gateway-developer-portal repo) where we manage api keys for our logged in users. The api key will decide whether they can invoke an our developer api (an apigateway endpoint) or not.
However, I will grant you that, if designed properly, the identity pool can give you some amazing capabilities, like for example restricting the right of the logged in user to read and write only DynamoDB entries that he / she owns. But, this is so hard to get right, that you end up with a role that is more permissive than it should be. In fact, the default policies you see when looking for CloudFormation template samples use way too many wildcards ('*'). And if you use a permissive policy, you end up negating the benefits of using roles, and therefore using identity pools.
So our use case in short:
I don't understand what an identity pool would bring, especially not in a developer api portal.
@lestephane Thanks for the detailed explanation. Your use case is different from mine. In your case, you can get away with logged in / not logged in decisions and API keys.
In my case it is not possible; the users must have their own private data. Implementing this without IAM roles would mean re-implementing something that IAM gives me for free (if the user can assume IAM roles).
Thanks for the explanation, now I see that your use case is different from mine.
This has become the most commented open issue, and the second most commented issue of all times. I rest my case.
@baharev Thanks for confirming that i don't need identity pools, making this issue irrelevant for me. Of course, I can't speak for anyone else following this issue. I was merely trying to find a way out of the dichotomy.
@lestephane There are valid use cases where you do not need IAM roles, and apparently your use case is such a use case. That's 100% OK. However, the situation may quickly change in the future if your requirements change, and then this issue will become relevant for you as well.
Indeed @baharev I will continue following this issue
This scenario renders AWS Amplify pretty useless if you want to sign users in using Cognito user pools and then have them interact with a Lex bot. The SDK just passes their identity/IAM token, and without any way to map a user pool id with an identity id, you can't personalise any chat bot response for an authenticated user. Seems particularly short sighted.
The way I've worked round it is to hack the interactions function to pass the pool user id via chat session data, but this is technically insecure since the authenticated user could specify any user id and have the chat content personalised for their id.
Here's a lambda function that resolves a userpool user or federated identity to an email address.
The function is using the serverless typescript template.
For the userPool, I do a listUsers request as per suggestion here.
For federation, I validate the token to ensure it's not been tampered with (not sure if CognitoIdentity
validates it internally), then call CognitoIdentity.getId
as per the suggestion above by @behrooziAWS.
Then, I compare the resolved identity to the request identity to ensure it's not a hijack attempt, if ok, I'll save the details into a DynamoDb table.
export const postAccountCurrentProfile: Handler = async (event: APIGatewayEvent, context: Context, cb: Callback) => {
const body = JSON.parse(event.body);
const provider = body.provider;
const requestIdentityId = getCognitoId(event); //retrieve the id from the event
var foundIdentityId: string;
var foundEmail: string;
if (provider === 'UserPool') {
const sub = event.requestContext.identity.cognitoAuthenticationProvider.split(':CognitoSignIn:')[1];
const request = {
AttributesToGet: [], // NOTE: I'm using email as my username so don't have to get any attributes
UserPoolId: process.env.user_pool_id,
Filter: `sub = "${sub}"`,
Limit: 1
};
const cognitoClient = new CognitoIdentityServiceProvider({ region: process.env.region });
const users = await cognitoClient.listUsers(request).promise();
foundEmail = users.Users[0].Username;
foundIdentityId = requestIdentityId;
} else if (provider === 'Google') {
const gClient = new gApi.OAuth2Client(process.env.google_client_id);
try {
await gClient.verifyIdToken({ idToken: body.token, audience: process.env.google_client_id });
} catch (x) {
console.error(x);
emptyResult(500, cb);
return;
}
const identity = new CognitoIdentity();
const params = {
IdentityPoolId: process.env.identity_pool_id,
Logins: {
'accounts.google.com': body.token
}
};
const response = await identity.getId(params).promise();
foundIdentityId = response.IdentityId;
foundEmail = token.email;
}
if (foundIdentityId !== requestIdentityId) {
sLog.error('Account hijack attempt', { requestIdentityId, foundIdentityId });
//TODO: send to sns queue
emptyResult(500, cb);
return;
}
okObjectResult({ identity: foundIdentityId, email: foundEmail }, cb);
};
Hope this helps someone.
This issue has become the most commented issue of all times (in this repo), and has 5 more days left to become 1 year old.
@hectomg What didn't you like about my solution above?
+1 for this feature request.
@ffxsam , the problem with doing anything client side is you cannot trust the client.
Anything coming from the client must be treated as potentially compromised and must be validated on the back-end against known good sources.
Fair point.
Please add this feature. It is clearly already present in some fashion on the backend, as @baharev has explained. Ridiculous that it's taken this long and still no satisfactory resolution!
+1 for this. It is really hard to grab (probably no way) the identity id in the cognito post confirmation trigger when you use the cognito identity id as a leading key. (Though we could use sub, but we have a special use case for not using sub in the user attributes).
If identityId was available in the claims object for a lambda proxy, like the sub / userId is. All my problems would go away.
+1 for this! I was happy to stumble across this and I'm surprised that it's still open.
My use case is to allow "host" users to upload "protected" images to S3, then let all types of users view the images when viewing a particular host. I couldn't find a way to attach the identity id to the hosts on the server side, since stateful authentication is required to retrieve the identity id, which only makes sense from the browser.
My understanding is that this is similar to or the same as what @baharev had to workaround:
I looked at the Cognito API reference, and it is weird too. For example:
- AdminGetUser does not seem to give me the identity ID.
- DescribeIdentity does not seem give me any user attributes.
- Somehow the mapping between users and identity IDs seem to be hidden. The only way I could recover it is through the login tokens.
My problem is namely:
- AdminGetUser does not seem to give me the identity ID.
This isn't really an AWS Amplify issue though, since the fault seems to lie with the AWS SDK. I hope I'm not commenting on the wrong thread. 🙈
@dylan-westbury & @chadgarlandscg ,
I'm a bit confused as to which IdentityId you're after.
If you're after the IdentityPool IdentityId, this in a lambdaa event, this is available via the
APIGatewayEvent.requestContext.identity.cognitoIdentityId
property, the APIGEvent class is available in the lambda event.
I can't find the docs for this class but it's listed at the below type link.
https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/aws-lambda/index.d.ts
Apologies if I misunderstand :)
@thrixton apologies for the late reply! I'm actually trying to do this in a Node server, not a Lambda.
My users ("guests" and "hosts") authenticate with Cognito via Amplify. Hosts can upload an image via Amplify, which saves the image under their protected folder in S3. Then, guests (authenticated or not) as well as other hosts can browse for hosts and see their saved image.
Hosts are registered via an admin API call, which signs them up with Cognito via the AWS SDK's adminCreateUser
call. Neither this call, nor adminGetUser
return the cognito identity id, which I need to store in my DB in order for all users to see the host's uploaded image in S3 via Amplify Storage. Therefore, I am forced (at least with my architecture) to rely on my web application to send up the cognito identity id on the initial host login.
Perhaps I am the one misunderstanding 🙈I'm so sorry if that's the case! My situation just seemed to closely mirror one of the original problems mentioned by @baharev here:
AdminGetUser does not seem to give me the identity ID.
Feel free to let me know if I'm doing something crazy or missing something obvious.
Thanks a bunch!!
@dylan-westbury & @chadgarlandscg ,
I'm a bit confused as to which IdentityId you're after.
If you're after the IdentityPool IdentityId, this in a lambdaa event, this is available via the
APIGatewayEvent.requestContext.identity.cognitoIdentityId
property, the APIGEvent class is available in the lambda event.
I can't find the docs for this class but it's listed at the below type link.
https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/aws-lambda/index.d.tsApologies if I misunderstand :)
Cognito Identity id can obtained from Lambda context yes, but it cannot be mapped to an actual user account in Cognito's user pool
@dylan-westbury & @chadgarlandscg ,
I'm a bit confused as to which IdentityId you're after.
If you're after the IdentityPool IdentityId, this in a lambdaa event, this is available via the
APIGatewayEvent.requestContext.identity.cognitoIdentityId
property, the APIGEvent class is available in the lambda event.
I can't find the docs for this class but it's listed at the below type link.
https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/aws-lambda/index.d.tsApologies if I misunderstand :)
@dylan-westbury & @chadgarlandscg ,
I'm a bit confused as to which IdentityId you're after.
If you're after the IdentityPool IdentityId, this in a lambdaa event, this is available via the
APIGatewayEvent.requestContext.identity.cognitoIdentityId
property, the APIGEvent class is available in the lambda event.
I can't find the docs for this class but it's listed at the below type link.
https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/aws-lambda/index.d.tsApologies if I misunderstand :)
Yes that is the correct identityId, but it's only available if the authorizer is set to AWS_IAM, as opposed to a cognito user pool authorizer
+1 for this feature request.
Throwing my +1 in the ring here, I was pretty stunned to see this was even an issue given that Amplify leads you into this scenario ie. Storage
module using Federated IDs but no way to relate them to User Pool IDs.
A simple extra field in one of the response or trigger payloads would seem to resolve this issue, no?
Alright, I spent way too long trying to come up with a reasonable approach to linking the user's sub
value from my Cognito User Pool to the IdentityId
value from the Cognito Federated Id, and the solution I came up with isn't perfect, but it's somewhat sane and at least verifiable.
I noticed that requests to the AppSync GraphQL API's are authorized using the idToken
value that is returned after a user authenticates via Cognito User Pools. ~That same token is also used in calls to the getId
method of the AWS.CognitoIdentity
JS SDK. (ahhaaaaaa)~
I decided to query the User's record that is stored within my custom DynamoDB User
table based on the user's sub
value that is returned after login. You can get this from the user claims object, ie.
<Authenticator
hideDefault={true}
amplifyConfig={awsconfig}
onStateChange={async authState => {
if (authState === "signedIn") {
const user = await Auth.currentAuthenticatedUser();
const { sub } = user.attributes;
try {
const user = await API.graphql(
graphqlOperation(getUser, { id: sub })
);
console.dir(user);
} catch (err) {
console.log("error loading user...: ", err);
}
}
>
Then, I wrote a custom lambda resolver for the federatedId
field on my User
schema that will fetch the DynamoDB record's federatedId
field if it isn't empty. If it _is_ empty, I then use the authorization token which has already been verified as valid by AppSync and send it to the AWS.CognitoIdentity.getId
method.
The resolver (Query.federatedId.req.vtl):
{
"version": "2017-02-28",
"operation": "Invoke",
"payload": {
"type": "Query",
"field": "federatedId",
"arguments": $utils.toJson($context.arguments),
"identity": $utils.toJson($context.identity),
"source": $utils.toJson($context.source),
"headers": $util.toJson($ctx.request.headers)
}
}
The lambda body:
// check your DynamoDB record here and only do the below
// if your record doesn't already contain the federatedId
// ...
const { authorization } = event.headers;
const { sub, iss, aud, token_use, email } = event.identity.claims;
const userName = event.identity.claims["cognito:username"];
if (iss !== `https://${ProviderName}`) {
//fail
callback("Unauthorized", null);
}
if (token_use !== "id") {
//fail
callback("Unauthorized", null);
}
if (aud !== clientId) {
// fail
callback("Unauthorized", null);
}
const params = {
IdentityPoolId,
Logins: {
"cognito-idp.us-east-1.amazonaws.com/us-east-1_UsErPoOLiDLoL": authorization
}
};
try {
const result = await cognitoidentity.getId(params).promise();
const { IdentityId } = result;
// todo: update DynamoDB record with this info where User.id == sub
// or User.userName === userName
callback(null, IdentityId);
} catch (err) {
console.log(err);
callback("Unable to resolve" + err, null);
}
There's also a few sanity checks above to ensure the claims are valid against my stack.
const IdentityPoolId = "us-east-1:12345678-this-aint-real-abcdefghijkl";
const ProviderName = "cognito-idp.us-east-1.amazonaws.com/us-east-1_UsErPoOLiDLoL";
const clientId = "YOUR_ISSUER_CLIENT_ID";
It's still a bummer that this is even required, it requires a user login event, and, I'm technically making a mutation on a User query which is bad news, but it feels like a better approach than others.
The other approach I explored was using adminLinkProviderForUser
on the User Pool Sign Up Confirmation trigger to proactively create the User's Federated ID, but the ProviderName
value generated by Amplify is too long to pass the Preflight Validation performed by Cognito (limited to 32 characters) and this approach would also only work for new users, not existing users.
Hope this helps someone!
EDIT Upon further inspection, it looks like the idToken
is only used as the authorization
token in the AppSync Console and _not_ in the amplify-js
library. You'd have to also override the library authorization method in your Amplify.configure
call in order to send the idToken
instead of the accessToken
@davekiss Thanks for sharing. Unfortunately, your struggle only underlines my point: It is unnecessarily complicated and brings in other services and libraries (GraphQL, AppSync, DynamoDB, Lambda in your workaround).
I see no good technical reason why the Cognito REST API could not support it directly for the user pool owner.
Pecked out from my iPhone
On Apr 7, 2019, at 1:46 PM, Ali Baharev notifications@github.com wrote:
@davekiss Thanks for sharing. Unfortunately, your struggle only underlines my point: It is unnecessarily complicated and brings in other services and libraries (GraphQL, AppSync, DynamoDB, Lambda in your workaround).
I see no good technical reason why the Cognito REST API could not support it directly.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.
After working with AWS and DynamoDB, it seems that management must be making technical decisions in order to increase revenue. Forcing these ugly workrounds which force you to use other services. DynamoDB can’t do some basic things without doing scans, etc
Hi,
The serverless-stack.com have this in their tutorial:
"Mapping Cognito Identity Id and User Pool Id"
https://serverless-stack.com/chapters/mapping-cognito-identity-id-and-user-pool-id.html
Thoug it only at runtime inside a Lambda function.
export async function main(event, context, callback) {
const authProvider = event.requestContext.identity.cognitoAuthenticationProvider;
// Cognito authentication provider looks like:
// cognito-idp.us-east-1.amazonaws.com/us-east-1_xxxxxxxxx,cognito-idp.us-east-1.amazonaws.com/us-east-1_aaaaaaaaa:CognitoSignIn:qqqqqqqq-1111-2222-3333-rrrrrrrrrrrr
// Where us-east-1_aaaaaaaaa is the User Pool id
// And qqqqqqqq-1111-2222-3333-rrrrrrrrrrrr is the User Pool User Id
const parts = authProvider.split(':');
const userPoolIdParts = parts[parts.length - 3].split('/');
const userPoolId = userPoolIdParts[userPoolIdParts.length - 1];
const userPoolUserId = parts[parts.length - 1];
}
It literally costed me half a day to realize it isn't doable.
I cannot express how misleading ${cognito-identity.amazonaws.com:sub}
is. Took me half a day to find out it isn't Cognito Pool's user sub, since it is the last thing I debugged.
Bridge between User Pool user attributes is a must I'd say.
Sad to see that the issue is from 2 years ago!!!
I hope this sample will your help.
(using lambda-proxy integration and cognito userpool authorizer in serverless framework)
https://github.com/terukizm/sls-lambda-proxy-userpool-auth-sample
@terukizm We know that it can be solved, but we think it should be solvable with the SDK only, that is, without any extra services, frameworks, and hoops to jump. Your solution underlines the problem: You needed an extra framework (serverless) and a lambda-proxy integration. It is not simple.
@baharev I'm chiming for another user who wants this functionality. Right now I'm using Cognito to manage all my users in my app, lambda to communicate with a dynamodb, and a JS front end using amplify accessing lambda without API gateway.
In Lambda I have access to context.identity.cognitoIdentityId and nothing else. I need access to a user's attributes in the user pool via cognitoIdentityId. Or vice versa being able to take a username in the user pool and access the cognitoIdentityId.
@Shadaeiou OK. And how can I help you? This is the feature that I have been asking for; I don't have an easy way of doing it either. I think you should address one of the developers at AWS to do something about it.
@baharev I wasn't asking for help I was just saying I'm also in the same boat and desire this feature.
@Shadaeiou Then please tell that to one of the AWS developers on this thread. This issue is the oldest open issue and also the most commented one. I don't understand why it has such low priority.
We all do. It’s shouldn’t be this hard. Not this hard on Google or Azure and bet. Or Oracle even
Pecked out from my iPhone
On Jul 27, 2019, at 7:43 PM, Shadaeiou notifications@github.com wrote:
@baharev I wasn't asking for help I was just saying I'm also in the same boat and desire this feature.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.
ah @baharev, you deserve a medal! Like @Can-Sahin said, it took me a day to realize that it still isn't possible and sad to see this has been a point of discussion for the last 2 years comparing how AWS is promoting Amplify as an inhouse serverless stack out there. Has this been raised in amplify-cli repo? I just have hit with the same issue but on a different context. Giving up on this one for now.
@babu-upcomer As for amplify-cli, the first red flag was when its predecessor (awsmobile-cli) silently and irrecoverably deleted the S3 bucket with the users' files. More red flags followed, then I ultimately stopped using amplify-cli when it required full admin access to my AWS account, and I refuse to use it. Due to the bugs I encountered, I simply don't trust it. Just look at the issues I raised in the awsmobile-cli, amplify-cli and amplify-js repos.
I have rewritten everything from Amplify on the top of the AWS JavaScript SDK that I need except Auth. The reason for not rewriting Auth is that it is non-trivial and I don't have the time at the moment to do it. But it is coming...
By the way, it is fairly easy to rewrite, for example, Storage (S3) on the top of the AWS JavaScript SDK: 15 lines of code, and then you have full control. Accessing DynamoDB is equally easy with the AWS JavaScript SDK.
I rolled my own deployment script, less than 200 lines and it does pretty sophisticated things (for example compression and cache control for CloudFront the way I want it). Now I control how Storage works, or how deployment is done. Again, look at the issues I raised in the awsmobile-cli, amplify-cli and amplify-js repos.
As for this particular issue #54, I am working towards an approach similar to what @lestephane sketched in his comment. It turns out that after restructuring my application, a similar approach works for me too. It is admittedly a workaround, but at least I will have full control. I will still depend on Auth though...
@babu-upcomer As a side note: Even if this issue is resolved, you would want to assign your unique ID to each user in a way you control. From the comment of @RossWilliams:
I’d much rather have a choice of claim to use and I’d like to avoid sub as it’s not under my control and cannot be recreated when restoring a Cognito User Pool. Anything that relies on a sub would be a large operations risk and require a lot of tooling and time to overcome.
in the related issue Can we use the sub instead of identity id for private/protected storage?
I cannot believe this problem has not been fixed yet. People are burning so much time!!!!
So, I also jump on the bandwagon. Getting another user's protected
file through AWS Amplify requires the identityId
such as:
Storage.get('test.txt', {
level: 'protected',
identityId: 'xxxxxxx' // the identityId of that user
})
.then(result => console.log(result))
.catch(err => console.log(err));
So, I should get that identityId
not for the current user, but also for other ones!
Lol, I was not expecting an issue this old to keep open. I'm facing exactly the same problem. I come from Google firebase and this was super easy to accomplish with the SDK. +1 to please solve this
This took us about 8H to solve, the tidiest secure solution we currently have for our closed AppSync + Cognito UP + Cognito IP / AWS system is running a Lambda as a mutation that takes no parameters. The user's exact current JWT is put into the Lambda request by the AppSync VTL resolver along with the CUP ID and then the Lambda requests the Cognito IP user-identity using the CognitoIdentity module and then writes it into the "Users" DDB table using the CUP ID so it can be looked up when needed.
AppSync Lambda-request:
{
"version" : "2017-02-28",
"operation": "Invoke",
"payload": $util.toJson({
"sub": $ctx.identity.sub,
"jwt": $ctx.request.headers.authorization
})
}
Lambda:
const AWS = require('aws-sdk')
const cip = new AWS.CognitoIdentity()
const ddb = new AWS.DynamoDB.DocumentClient()
exports.handler = async ({ sub, jwt }, context, callback) => {
let { IdentityId: id } = await cip.getId({ IdentityPoolId: process.env.IDENTITY_POOL_ID, Logins: { [process.env.USER_POOL_ID]: jwt } }).promise()
await ddb.update({ TableName: 'Users', Key: { id: sub }, UpdateExpression: 'set identityToken = :id', ExpressionAttributeValues: { ':id': id } }).promise()
callback(null, {})
}
Hi all, I'm going to be honest and say that despite a fair amount of reading I'm not entirely sure if the question that I have is related to this ticket or not. I'm just getting into Amplify really, and Cognito, so it's a bit confusing to me at the moment.
My specific use case (I'm hoping somebody can tell me if I'm just crazy) is that I am using PubSub
with Amplify, and when a user registers I need to setup some permissions - basically I need to do the equivalent of aws iot attach-principal-policy --policy-name 'myIoTPolicy' --principal '<YOUR_COGNITO_IDENTITY_ID>'
but I'd like to automate this on user signup.
This would require that in the PostAuthentication_Authentication
trigger I am able to get the users Cognito Identity ID... but from what I can tell, that's not possible?
It seems like perhaps the reason this isn't possible should be more obvious, but there seems like there should be SOME relationship between the user pool user and the federated cognitio identity? But from what I can tell, within these hooks I don't have access.
I'm guessing the fact that this is so confusing to me shows there is a fundamental (and significant!) gap in my understanding of how these systems should be used.
My plan for now, to work around this, is to use a different trigger (an API call) where I know I have access to the Cognito Identity ID. I guess this is likely because when you are making API calls, you're doing so as a federated user, not as a user from a "user pool"?
@kevin-mitchell If I interpret your post properly: Yes, problem well spotted, and yes, apparently that's the best workaround for the time being. Welcome to the club!
My specific use case (I'm hoping somebody can tell me if I'm just crazy) is that I am using
PubSub
with Amplify, and when a user registers I need to setup some permissions - basically I need to do the equivalent ofaws iot attach-principal-policy --policy-name 'myIoTPolicy' --principal '<YOUR_COGNITO_IDENTITY_ID>'
but I'd like to automate this on user signup.This would require that in the
PostAuthentication_Authentication
trigger I am able to get the users Cognito Identity ID... but from what I can tell, that's not possible?My plan for now, to work around this, is to use a different trigger (an API call) where I know I have access to the Cognito Identity ID. I guess this is likely because when you are making API calls, you're doing so as a federated user, not as a user from a "user pool"?
This is exactly my use case. We're using an AppSync mutation for that right now to trigger the policy attachment. But, yeah, this is definetily not the clean way...
What we are doing to solve this problem, which has worked ok so far, is we set the user pool Username
to match the IdentityId
from the identity pool when creating a new user. Since they match, the bidirectional mapping between the two services is now pretty trivial ;)
The new user signup flow goes like this:
IdentityId
from the identity pool using getId()
signUp()
on the user pool, passing in the IdentityId
from the previous call as the new user's Username
(we put their human-readable username in preferred_username
)getCredentialsForIdentity()
using same IdentityId
/Username
from before along with the IdToken
from the user poolWe implemented this first with the lower-level SDK's linked to above. I didn't handle the mapping of this up to amplify but I understand that was a non-trivial task.
And obviously this won't work so well if you already have a bunch of users, but if you're building a new app like we are, hopefully this works for you too.
I am going to add in another possible solution here, but it may be of limited value and may have other drawbacks. I will not profess to be any kind of AWS or Amplify expert so if you see an issue with using this approach I would be happy to hear from you.
I found that it is possible to use a lambda authorizer (of type TOKEN, other types may be possible, but I did not test). We were already utilizing a lambda authorizer to add additional context information into our APIs so this seems like a good fit. It's still early days so I'm not sure if we'll hit any rate limits or other problems. Consider this to be an untested/alpha solution.
get_id
method from the cognito-identity
client.IdentityId
. Use this IdentityId
to call attach_principal_policy
from boto3's iot
client.Example snippet from my API's lambda authorizer:
import os
import boto3
CLIENT_COGNITO = boto3.client('cognito-identity')
CLIENT_IOT = boto3.client('iot')
IDENTITY_POOL_ID = os.environ.get('IDENTITY_POOL_ID')
IOT_TOPIC_POLICY = os.environ.get('IOT_TOPIC_POLICY')
REGION = os.environ.get('REGION', 'us-east-1')
USER_POOL_ID = os.environ.get('USER_POOL_ID')
def setup_iot(token, context):
account_id = context.invoked_function_arn.split(":")[4]
logins = {}
logins['cognito-idp.{}.amazonaws.com/{}'.format(
REGION, USER_POOL_ID)] = token
id_response = CLIENT_COGNITO.get_id(
AccountId=account_id,
IdentityPoolId=IDENTITY_POOL_ID,
Logins=logins
)
identity_id = id_response['IdentityId']
CLIENT_IOT.attach_principal_policy(
policyName=IOT_TOPIC_POLICY,
principal=identity_id)
def lambda_handler(event, context):
token = event['authorizationToken']
claims = verify(token) # not shown, but responsible for verifying the JWT
principal_id = claims.get('cognito:username', None)
if not principal_id:
raise Exception('Unathorized')
setup_iot(token, context)
<other code removed>
Surprised to see that this issue is open since Dec 2017. Neither has this been fixed nor documented to help customers. Based on a cursory read and the no. of hacks listed on this issue, it seems obvious that it should not be left to customers and should be a P0 priority in terms of features.
Ours is a SaaS application using AWS Amplify and we need a link between the Cognito Identity ID and the username for the below-mentioned use cases:
Not sure which team is taking it up (Cognito or Amplify) but it seems that this is a major issue for a lot of customers. Sorry if this sounds rude but it's a valid critical concern and we haven't been able to get around this.
Due to this issue, I will never use AWS Cognito again. Likewise, I will not be using or recommending Amplify as well due to this issue being completely disregarded.
I think we should make a template and send a support inquiry to AWS with the same content pointing to this issue, demanding an answer. We're paid customers, and it's been 2 years and counting that we had no conclusive response other than someone coming and adding a few labels to the issue saying that it's on a backlog.
Amplify is full of surprises. Easy things are not simple as they say. This issue should not be open till now.
This problem has come back to bite me in my 5th project so far with Amplify. It makes no sense that there is no bi-directional mapping between User Pool and the Identity Pool identities. Especially, in case of Federated user pool. I have federated User pool with SAML, Google, and Facebook and mapping which user is the identity in the identity pool has been a pain in the behind unless we do some hacks like reading the JWT idToken to get the Identity ID and then store it somewhere for later reference.
This also makes it super painful to use the Storage
module especially the protected
level since protected levels needs the identity ID to access a user's file. This is fine for the current user and they can access their "own" file(s) from the private
and protected
prefixes in S3, but what about accessing (GetObject) other user's files from the protected
prefix since the definition of protected
says you can read any other user's protected file but not write to it. This is painful because now the current user needs to know ALL the other user's identity ID in advance of making use of the Storage.get
API. This to me sounds more like an issue for AWS Cognito team to solve than for Amplify.
Hmm, this is an early sign of AWS abandoning Amplify. I would recommend anyone to move away from Amplify whenever possible. There have been so many promises in the documentation at-least with the way it was written but then the product manager like me end up burning my dev's time just to hit into a dead end in future.
@babus Amplify is not being abandoned by any means, in fact we invest into it more every day. We are in discussions on this issue with the Cognito team.
I think Amplify has had some major improvements since AWS Mobile Hub days, but I think specific to Cognito as a service, there's also much work to be done. I am eager and excited to see those improvements coming up in the future.
@undefobj That's glad to hear. But let's solve it first and then talk. This issue is basically a very root issue with not so ever understanding of how cognito and storage work together and it's been here for the past 2 years.
To the downvoters, people are paying to AWS by using Amplify and this isn't just a open source library where I cannot be toxic. This is a paying customer expecting some basic level of customer satisfication from the vendor. Even their business level technical support team doesn't have any clue when this would be solved.
I can't believe this thread is still open.... I'll just mention that I am facing the same problem albeit different.
I am using the CognitoID as the UID for users in my own DB. No problems except when the project scope changed to allow 'admin' users to add other users to their application, there is no way of getting this ID when creating the user in the UserPool.
Just here to say that this issue is 855 days old.
Way to go Cognito team!
@undefobj do you have any feedback from the Cognito team please?
I tried the solution @djkmiles provided to no avail. Worked out it's because I'm using AWS_IAM as my appsync authentication type rather than cognito...
BUT I was inspired to check what treasures were hidden inside $ctx.identity
, logging the whole thing in lambda gives the following:
{
accountId: ----,
cognitoIdentityAuthProvider: ----,
cognitoIdentityAuthType: 'authenticated',
cognitoIdentityId: ----, <--- FUCK YEAH
cognitoIdentityPoolId: ----,
sourceIp: ----,
userArn: ----,
username: ----,
}
sure enough, storing and then using that cognitoIdentityId
lets me access users protected files on S3.
thanks to @djkmiles for getting me out of this hell hole - hope this proves useful to others
Whoever gets here, maybe this bit helps, if you're inside a lambda function accessed by an authenticated user:
https://serverless-stack.com/chapters/mapping-cognito-identity-id-and-user-pool-id.html
Without having bidirectional mapping between users and their identities in cognito, how are we supposed to find out the dangling identities(i.e identities with no user in userpool). Now we have to do extra work to store the mapping elsewhere(in db or userpool attribute) so we are able to delete the dangling identities.
How is this issue still not solved? Is there some technical debt in the background that makes this impossible to solve? Makes Amplify useless for me (because I will need to duplicate information elsewhere) and surely many others.
Just come back to an older Cognito project, hit this problem over a year ago. Can't believe there isn't any official way of resolving this still.
How can I find the IdentityId from a cognito sub or the other way around, without a user session? I have a table with information only linked to IdentityId, but I don't know which cognito-user they are linked to. Is it possible figure out the cognito users somehow?
that's the problem in this discussion. I ended up building a mapping table
to match them.
On Wed, May 27, 2020 at 6:49 PM sveins12 notifications@github.com wrote:
How can I find the IdentityId from a cognito sub or the other way around,
without a session user session? I have a table with information only linked
to IdentityId, but I don't know which cognito-user they are linked to. Is
it possible figure out somehow?—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/aws-amplify/amplify-js/issues/54#issuecomment-635023937,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/ADCEXAOT22MO4EEE5HP4SYDRTWYJZANCNFSM4EHSZZ5Q
.
+1
Sitting with the same problem now. We have an iOS client that associated users with cognito identity IDs and the project lambdas use the cognito identity ID. Now we want to add a web client that uses oidc and cognito user pool authorizers which as far as I can tell does not expose the cognito identity ID. I now need to figure out how to get the cognito identity ID from the info that is in the request context authorizer claims, otherwise I am going to have a hell of a time refactoring the whole API and iOS client to support the new web client or have to create new lambda versions for the web client API.
Another way to get the identity id inside the lambda with cognito auth:
https://gist.github.com/dtelaroli/78f8a17afceed3e7d398929f0fbbf950
Is there any timeline we can get on when this basic functionality will be available? On my services backend we are creating many user accounts, importing from json.. but we need to store the IdentityId of each account at the time of creation. For some reason getId only supports unauthenticated access.. when I supply the Login params with a proper token, the IdentityId that's returned is not the same as a user gets when they log in using amplify..
Is anyone from the service team aware of this issue? @undefobj it's been "in discussion" and kicked around for years
After looking around in the Amplify files in Swift, I found that one can retrieve the identityId by fetching the current auth session, casting the result.get() as AuthCognitoIdentityProvider, and running .getIdentityId().get(). Same process as retrieving the userSub. It seems this functionality may have finally been added!
This solution works for me.
hey guys,
just to re-iterate that this is a really big problem with Cognito and really needs to be addressed. It's clear that there's multiple use cases associated with this that are all important and with only allowing this value to be managed on the client side, there's a massive security flaw in every single project that requires the cognito Identity Id on the backend. Ultimately, if a user knew of this issue and knew the only work around is to set the cognitoIdentityId through a front end API request with no backend validation (essentially setting it to any value), I'm pretty sure there's a big risk of malicious attacks at worst and at best, breaking an application.
By the looks of it, the only bi-directional map that exists is between the IdToken and the cognito Identity pool and perhaps the technical debt in either adding a new mechanism without making it a "Login" in the federated Identity object is too great but even if that is the issue, it's been nearly 3 years.... make it ugly if you need to, patch over it need be, but please please please just fix this issue or document and supply code for a work around that is reasonable
What we did to resolve this in our use case was drop OICD and user pool authorizers and went with https://github.com/aws/aws-aspnet-cognito-identity-provider. We then exchange the token for IAM credentials and call our lambdas (API gateway) in the same way our iOS app does. It required a lot of hammering to play nicely with Blazor server and flat out failed with Blazor WASM. It was an very painful exercise.
Save public so just lookup by identityId
Auth.currentUserInfo() .then(info => { Storage.put( identityId + '_userInfo.json', JSON.stringify(info), { level: 'public', contentType: 'application/json' } ); });
It seems like a very bad advise from security point of view. As @baharev explains above this mapping must be done purely from your backend
At react-native client code I pass JWT token to IAM authorized lambda
Promise.all([Auth.currentSession(), Auth.currentCredentials()])
.then((v) => {
jwtToken = v[0].getIdToken().getJwtToken(); // holds user info like sub, IdP User Id
credentials = Auth.essentialCredentials(v[1]);
const lambda = new Lambda({ credentials: credentials, region: ... });
return = lambda.invoke({
FunctionName: '...',
Payload: JSON.stringify({ myPayload: {....}, jwtToken: jwtToken }),
}).promise();
})
At my IAM authorized lambda I do make sure that the passed JWT token resolves to the same Cognito Identity as in the call context.
exports.handler = async (event, context) => {
cognitoIdentity.getId(
{
AccountId: '12345676890',
IdentityPoolId: context.identity.cognitoIdentityPoolId,
Logins: {
`cognito-idp.${region}.amazonaws.com/${userPoolId}`: event.jwtToken,
},
},
)
.promise()
.then((d) => assertIdsAreTheSame(d.IdentityId, context.identity.cognitoIdentityId))
.catch((e) => check_id.e = e);
....
}
This way I know the user Cognito Identity and the user info like email, sub, IdP UserId which is enough to find user at UserPool or my dynamo DB
@ffxsam had it right, depending on the complexity of your use case (for instance, specially-permissioned S3 operations):
The SDK should handle these sorts of lower level operations for us.
@baharev please provide a bit more clarity on your comment:
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.
^ as of today, we make this info (name, email, etc.) accessible to the authenticated client. This isn't going to change.
Amplify gives you the tools you need to provision endpoints that utilize the Cognito API's admin endpoints. The JS library itself is geared towards front-end developers though. I'd advise...
Just to reiterate the solutions:
GetUser
request, whose response contains the data for which you're looking.The user-specific S3 bucket discussion seems to be another issue unto itself. Please open a new issue for that if you'd like.
@harrysolovay I stopped using Amplify an year ago; this issue was one of the many reasons why. As far as I am concerned, you could have closed this issue an year ago. As a side note, I find it quite amusing how the Amplify team "solved" this issue, which is the most commented and oldest open issue, in almost 3 years time.
@baharev I understand your frustration. If you ever decide to try Amplify again, please tag me personally in any issue. I hope you’ll give the framework another chance.
@baharev If you'd like to chat with myself our @mauerbac, please feel free to reach out to us on our Discord channel or through DMs.
@harrysolovay @sammartinez I re-wrote everything I needed an year ago (S3, Lambda, API Gateway, DynamoDB, Cognito, CloudFront, CloudWatch, Kinesis Firehose, SES, SNS, CloudFormation and a deployment script in Python). The only non-trivial piece was the Cognito authentication (SRP and handling the tokens); all the other services are well-documented and have well-thought-out REST API and they are, for the most part, pleasant to work with. At this point I don't see any reason why I would want to use Amplify. I apologize if that was harsh but that's how it is.
Not sure If i'm over simplifying this but this worked for me to map my Cognito identity ID path for S3.
const [test, setTest] = useState(["Loading.."]);
let test = await Auth.currentUserInfo();
setTest(test.id);
<h1>{test}</h1>
Is this still a thing? This issue is so old that I'm not sure what was solved and what not.
Can we trust the event that generates API Gateway if an authenticated user makes a call? From a backend in Lambda, triggered by API Gateway (REST API created with the amplify-cli), within the event I can get these two:
event['requestContext']['identity']['cognitoIdentityId']
event['requestContext']['identity']['cognitoAuthenticationProvider']
The cognitoAuthenticationProvider
string has the sub
from the user at the end after a colon (like cognito-idp.eu-west-1.amazonaws.com/eu-west-XXXXXXXX,cognito-idp.eu-west-1.amazonaws.com/eu-west-XXXXXXXX:CognitoSignIn:1234asdf-12ab-12ab-12ab-123456asdfgh
). You can also get the User Pool id from here.
Most helpful comment
@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!