amazon-web-servicesaws-lambdaaws-api-gatewaymicronautmicronaut-aws

How to obtain "requestContext" data in a Micronaut API implementation being part of a AWS Lambda Proxy


In an AWS Lambda Proxy (being an integration in an API gateway which uses Cognito authorization) I'd like to obtain the user ID when handling a request. The Lambda is written in Java using Micronaut. The same Lambda is used as the integration of several API endpoints.

I found out that the Cognito user ID is contained in the requestContext entry of the proxy data given to the Lambda's handler:

public class Handler implements RequestStreamHandler {
    private static MicronautLambdaContainerHandler handler = new MicronautLambdaContainerHandler();

    @Override
    public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException {
        // `input` contains the information I need (see below)
        handler.proxyStream(input, output, context);
    }
}

When invoking the Lambda via the API while being authenticated as a Cognito user, the input steam looks like this (some details omitted / changed to exemplary values and the Cognito user ID being marked with // !!!):

{
    "resource": "/test",
    "path": "/test",
    "httpMethod": "GET",
    "headers": {
        "accept": "application/json, text/plain, */*",
        "accept-encoding": "gzip, deflate, br",
        "accept-language": "en-US,en;q=0.5",
        "Authorization": "Bearer eyJraWQiOiJPWlgzYVg3UWNITFwvM09vODg4SzhaYjBlcmRJMjZNNWFRdXF3a3VyZWhaVT0iLCJhbGciOiJSUzI1NiJ9[...]",
        "cache-control": "no-cache",
        "Host": "api.my-app.example.com",
        "origin": "https://my-app.example.com",
        "pragma": "no-cache",
        "referer": "https://my-app.example.com/home",
        "User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0",
        "X-Amzn-Trace-Id": "Root=1-5ebbd0f0-[...]",
        "X-Forwarded-For": "[...]",
        "X-Forwarded-Port": "443",
        "X-Forwarded-Proto": "https"
    },
    "multiValueHeaders": {
        /* similar to above but values being arrays */
    },
    "queryStringParameters": null,
    "multiValueQueryStringParameters": null,
    "pathParameters": {},
    "stageVariables": null,
    "requestContext": {
        "resourceId": "927cr8",
        "authorizer": {
            "claims": {
                "sub": "c99202cc-e088-43d6-8c15-1fd73a717a7c",    // !!!
                "cognito:groups": "[...]",
                "iss": "https://cognito-idp.eu-central-1.amazonaws.com/eu-central-1_[...]",
                "cognito:username": "c99202cc-e088-43d6-8c15-1fd73a717a7c",    // !!!
                "aud": "[...]",
                "event_id": "0d509360-d81e-4e7e-b346-9d018ed1cd04",
                "token_use": "id",
                "custom:[...]": "[...]",
                "auth_time": "1587536524",
                "name": "[...]",
                "exp": "Wed May 13 11:45:53 UTC 2020",
                "iat": "Wed May 13 10:45:53 UTC 2020",
                "email": "[...]"
            }
        },
        "resourcePath": "/test",
        "httpMethod": "GET",
        "extendedRequestId": "Md2VmF0OFiAFhZA=",
        "requestTime": "13/May/2020:10:50:24 +0000",
        "path": "/test",
        "accountId": "[...]",
        "protocol": "HTTP/1.1",
        "stage": "default",
        "domainPrefix": "api",
        "requestTimeEpoch": 1589367024516,
        "requestId": "feb2c8b2-4cf6-405b-bc48-76ebe33fde62",
        "identity": {
            "cognitoIdentityPoolId": null,
            "accountId": null,
            "cognitoIdentityId": null,
            "caller": null,
            "sourceIp": "[...]",
            "principalOrgId": null,
            "accessKey": null,
            "cognitoAuthenticationType": null,
            "cognitoAuthenticationProvider": null,
            "userArn": null,
            "userAgent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0",
            "user": null
        },
        "domainName": "api.my-app.example.com",
        "apiId": "[...]"
    },
    "body": null,
    "isBase64Encoded": false
}

The MicronautLambdaContainerHandler then does a lot behind the scenes which I don't fully understand yet; however, in the end I am able to define API endpoints using micronaut's annotations in a controller class:

@Controller("/")
public class TestController {

    @Get("/test")
    public HttpResponse<String> test(HttpRequest<?> request) {
        String userId = ?
    }

}

This example is a request handler for GET /test.

The HttpRequest object contains everything from the original request like headers and stuff, but not the information the AWS gateway adds to this, like the result of the authentication.

How can I now access that, in particular the requestContext, which was handed to the Lambda via its input? I'm missing some piece of the puzzle here.


Solution

  • Micronaut defines two typed binders called AwsProxyRequestArgumentBinder and ContextArgumentBinder. Based on the documentation type bound parameters can just be asked for in the method arguments by their type.

    So you should be able to use one of these:

    import com.amazonaws.services.lambda.runtime.Context;
    import com.amazonaws.serverless.proxy.model.AwsProxyRequest;
    
    @Controller("/")
    public class TestController {
    
        @Get("/test")
        public HttpResponse<String> test(HttpRequest<?> request, Context context) {
            String userId = context.getAuthorizer().getClaims().getSubject();
        }
    
        @Get("/test")
        public HttpResponse<String> test2(HttpRequest<?> request, AwsProxyRequest awsRequest) {
            String userId = awsRequest.getRequestContext().getAuthorizer().getClaims().getSubject();
        }
    
    }
    

    If that doesn't work, maybe just copy what those two binders do from their source code.

    source.getAttribute(RequestReader.LAMBDA_CONTEXT_PROPERTY);
    

    or:

    ((MicronautAwsProxyRequest<?>) source).getAwsProxyRequest();