OK I am now days into this and have made significant progress but am still completely stumped about the fundamentals.
My application uses Cognito User Pools for creating and managing users - these are identified on S3 it seems by their IdentityId. Each of my users has their own S3 folder, and AWS automatically gives them a folder name that is equal to the user's IdentityId.
I need to relate the IdentityId to the other Cognito user information but cannot work out how.
The key thing I need is to be able to identify the username plus other cognito user attributes for a given IdentityId - and it's insanely hard.
So the first battle was to work out how to get the IdentityId when a Cognito user does a request via the AWS API Gateway. Finally I got that worked out, and now I have a Cognito user, who does a request to the API Gateway, and my Lambda function behind that now has the IdentityId. That bit works.
But I am completely stumped as to how to now access the Cognito user's information that is stored in the user pool. I can't find any clear information, and certainly no code, that shows how to use the IdentityId to get the Cognito user's attributes, username etc.
It appears that if I use a "Cognito user pool" to authorize my method in API Gateway, then the body mapping template can be used to put Cognito User information such as the sub and the username and email address into the context, BUT I do NOT get the IdentityId.
BUT if I use the AWS_IAM
to authorize my method in the API gateway then the body mapping template does the inverse - it gives me the IdentityId but not the Cognito user fields such as sub and username and email.
It's driving me crazy - how can I get the IdentityId and all the Cognito users fields and attributes together into one data structure? The fact that I seem to be only able to get one or the other just makes no sense.
You can't change standard user pool attributes after a user pool is created. Instead, create a new user pool with the attributes that you want to require for user registration. Then, migrate existing users to the new user pool by using an AWS Lambda function as a user migration trigger.
Step 3: Configure Cognito Authorizer for API GatewayGo to “Resources” and select “GET” method. Select “Method Request” configuration on right pane. Select “Cognito_Authorizer” in “Authorization” drop-down. That should automatically add a new field “OAuth Scopes”.
To allow users to run Lambda with their Amazon Cognito permissions, follow these steps: Use the API Gateway console to establish your Amazon Cognito user pool as an authorizer. Then, assign the Amazon Cognito user pool as the authorizer for the method of your API.
It turns out that to get the IdentityId AND user details at the same time using AWS Lambda/Cognito/API Gateway, you need to have a Lambda function that is authenticated using AWS_IAM
(NOT COGNITO_USER_POOLS
), you must send your request the AWS API Gateway, BUT it MUST be a signed request, you must then modify the integration request body mapping templates so that you are given the IdentityId in the event (maybe the context? can't remember). Now you have the IdentityId. Phew. Now you must submit the client's Cognito ID token from the front end to the back end. It is important to validate the token - you cannot trust that it has not been tampered with if you do not validate it. To decode and validate the token you must get the keys from your userpool, put them into your script, ensure that you have jwt decoding libraries plus signature validation libraries included in your AWS lambda zipfile. Now your script must validate the token submitted from the front end and then you can get the user details out of the token. Voila! Now you have both IdentityId plus user details such as their sub, username and email address. So easy.
The above is what is takes to get the username associated with an IdentityId using AWS Cognito/Lambda/API Gateway. This took me days to get working.
Can I please say to any Amazon employees who wander across this ........ well it's WAY too hard to get the user details associated with an IdentityId. You need to fix this. It made me angry that this was so hard and burned so much of my time.
The solution:
I did this by modifying an Amazon employees custom authorizer here: https://s3.amazonaws.com/cup-resources/cup_custom_authorizer_lambda_function_blueprint.zip
as found and described here: https://aws.amazon.com/blogs/mobile/integrating-amazon-cognito-user-pools-with-api-gateway/
use strict'; let util = require('util'); var jwt = require('jsonwebtoken'); var jwkToPem = require('jwk-to-pem'); var userPoolId = 'YOUR USERPOOL ID'; var region = 'YOUR REGION'; //e.g. us-east-1 var iss = 'https://cognito-idp.' + region + '.amazonaws.com/' + userPoolId; //https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html // DOWNLOAD FROM https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json let userPoolKeys = {PUT YOUR DOWNLOADED USER POOL KEYS JSON HERE}; var pems = {}; let convertKeysToPems = () => { var keys = userPoolKeys['keys']; for(var i = 0; i < keys.length; i++) { //Convert each key to PEM var key_id = keys[i].kid; var modulus = keys[i].n; var exponent = keys[i].e; var key_type = keys[i].kty; var jwk = { kty: key_type, n: modulus, e: exponent}; var pem = jwkToPem(jwk); pems[key_id] = pem; } } exports.handler = function(event, context) { convertKeysToPems() console.log(event); let token = event['body-json'].cognitoUserToken; console.log(event['body-json'].cognitoUserToken); ValidateToken(pems, event, context, token); }; let ValidateToken = (pems, event, context, token) => { //Fail if the token is not jwt var decodedJwt = jwt.decode(token, {complete: true}); console.log(decodedJwt) if (!decodedJwt) { console.log("Not a valid JWT token"); context.fail("Unauthorized"); return; } //Fail if token is not from your UserPool if (decodedJwt.payload.iss != iss) { console.log("invalid issuer"); context.fail("Unauthorized"); return; } //Reject the jwt if it's not an 'Access Token' if (decodedJwt.payload.token_use != 'id') { console.log("Not an id token"); context.fail("Unauthorized"); return; } //Get the kid from the token and retrieve corresponding PEM var kid = decodedJwt.header.kid; var pem = pems[kid]; if (!pem) { console.log(pems, 'pems'); console.log(kid, 'kid'); console.log('Invalid token'); context.fail("Unauthorized"); return; } //Verify the signature of the JWT token to ensure it's really coming from your User Pool jwt.verify(token, pem, { issuer: iss }, function(err, payload) { if(err) { context.fail("Unauthorized"); } else { let x = decodedJwt.payload x.identityId = context.identity.cognitoIdentityId //let x = {'identityId': context['cognito-identity-id'], 'decodedJwt': decodedJwt} console.log(x); context.succeed(x); } }); }
This problem -- the problem of using the user's sub
instead of their identityId
in S3 paths and how to set that up -- is 100% solved for me thanks to the help of @JesseDavda's solution to this problem in this issue: https://github.com/aws-amplify/amplify-js/issues/54
For all of you developers who have been trying to get the identityId
in lambdas so that your Amplify default paths in S3 work - this solution simply ends up ignoring identityId
altogether - it is a solution that sets up the paths in S3 based on sub
instead of the identityId
. At the end of this solution, you will never have to deal with more than one id for your users, you will never have to deal with identityId
(hopefully) ever again.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With