AWS Lambda API gateway with Cognito - how to use IdentityId to access and update UserPool attributes?

Duke Dougal picture Duke Dougal · Feb 22, 2017 · Viewed 8.5k times · Source

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.

Answer

Duke Dougal picture Duke Dougal · Feb 23, 2017

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);
      }
    });
}