Can you get custom claims using a User Pool authorizer and api gateway?

0

Can you get custom claims in an API Gateway v2 proxy lambda?

I created an API gateway configuration in CDK, using this as an authorizer:

        this.authorizer = new HttpUserPoolAuthorizer(stack.config.apiAuthorizer, userPool, {
            userPoolClients: [ userPoolCLient ]
        });

That works great. Then in my lambda I'm trying to get custom claims like this:

export function getAllCustomers(request: APIGatewayProxyEventV2WithJWTAuthorizer, context: Context): Promise<APIGatewayProxyResultV2<GetCustomersResponse>> {
    const claims = request.requestContext.authorizer.jwt.claims;

but here's what I get:

{
  "claims": {
    "auth_time": "1675018035",
    "client_id": "xxxxxx",
    "event_id": "031bcda9-f10d-4132-abc7-xxxx",
    "exp": "1675021635",
    "iat": "1675018035",
    "iss": "https://cognito-idp.us-east-1.amazonaws.com/us-east-xxxxxx",
    "jti": "1a6cf3b9-f809-412a-bc68-xxxx",
    "origin_jti": "80bb0296-6acc-47f8-8963-e6a2d9f0a26c",
    "scope": "aws.cognito.signin.user.admin",
    "sub": "xxxxxx",
    "token_use": "access",
    "username": "xxxxxx"
  },
  "scopes": null
}

I expected that it would pass all the claims along, so I would get given_name, email, my custom user pool attributes. Is there any way to do this short of creating a totally custom authorizer? Or, is the raw token available somewhere, and I can just decode it? It would be nice to not have to make a cognito call with the sub in order to retrieve all the attributes every time.

UPDATE: I think I figured out what's going on. The attributes I am looking for would be in the ID token, but what's used for the API request is the access token which doesn't have all those detailed claims in it. I think this means I'm forced to look them up from the user id every time. I don't think that's a cost issue (I don't see API call request pricing for cognito) but it's a delay, and if I'm decoding the access token myself I probably really ought to be using jwks to check the token, too. At that point maybe I don't need to use an authorizer at all, since my lambda is going to be doing all the work. Is there a better strategy here? I already have a preauth trigger but again that seems to modify the id token not the access token.

--C

profile picture
wz2b
asked a year ago961 views
1 Answer
0
Accepted Answer

I am going to answer my own question but I'm interested in whether or not people think this is good or bad. If nothing else, this might serve as an example for somebody else who stumbles across this post trying to do the same thing.

It looks like if you have an access token you can always call GetUser to get the ID. The access token contains an issuer URL which must be how CognitoIdenttityProviderClient knows how to follow back to the correct user pool. This seems to work fine. it's not clear to me whether or not GetUser checks the validity of the token, but I suspect it must because it says the access token must be unexpired. The docs don't explicitly say this, though. (Can anybody confirm GetUser thoroughly validates the access token? Seems like It must.)

export function getAllCustomers(request: APIGatewayProxyEventV2WithJWTAuthorizer, context: Context): 
        Promise<APIGatewayProxyResultV2<GetCustomersResponse>> {

    /* get the authorization header from API gateway v2 proxy event */
    const authHeader = request.headers['authorization'];
    if (authHeader === undefined) {
        return Promise.resolve({
            body: "No bearer token",
            statusCode: http2.constants.HTTP_STATUS_FORBIDDEN
        })
    }

    /* See if it starts with Bearer implying it's an access token */
    if(!authHeader.startsWith("Bearer ")) {
        return Promise.resolve({
            body: "Auth header is invalid",
            statusCode: http2.constants.HTTP_STATUS_FORBIDDEN
        })
    }


    const accessToken = authHeader.substring(7)
    const cc = new CognitoIdentityProviderClient({})
    const ccReq = new GetUserCommand({AccessToken: accessToken});

    return cc.send<GetUserCommandInput, GetUserCommandOutput>(ccReq)
        .then((result: GetUserCommandOutput) => {
           /* do something useful now that we have the full ID */
   ...
profile picture
wz2b
answered a year ago

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.

Guidelines for Answering Questions