Enriching requests with an AWS Lambda Authorizer

Profile picture

Joery Vreijsen – 28 March 2019
766 words in about 4 minutes

What is an Amazon API Gateway Lambda Authorizer exactly, and what can it do? As the name suggests a Lambda Authorizer placed on an Amazon API Gateway can provide authorization on requests, but did you know it can also enrich the request with additional session information?

What is an Amazon API Gateway Lambda Authorizer?

A Lambda Authorizer (formerly known as a custom authorizer) placed on an API Gateway is a Lambda function that controls access to your API endpoints. By returning a PolicyDocument the lambda can decide whether or not the request is allowed to pass through to the API Gateway.

As you know Lambda’s come in all shapes and forms like Node.js, Python, Java and many more. For this little tutorial we are going to stick with Java 8 as there are not a lot of Lambda tutorials out there using Java.

Creating a Java 8 Lambda Authorizer

Let’s create a basic Maven Project and add our only two dependencies.

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-lambda-java-core</artifactId>
    <version>1.2.0</version>
</dependency>

<dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-lambda-java-events</artifactId>
    <version>2.2.5</version>
</dependency>

The Java project only needs one class, here named Authorizer, that implements the Lambda’s RequestHandler<T, T> interface.

1
2
3
4
5
6
public class Authorizer implements RequestHandler<T, T> {

    public T handleRequest(T request, Context context) {

    }
}

As you can see, the RequestHandler<T, T> interface from Amazon takes a generic input, and output type.

In our case we would like to extract a Bearer token from the original request and enrich the request with the username placed in a specific header. We first need to get our Authorization header from the original request, using the APIGatewayProxyRequestEvent . This class will give us access to our incoming Request data.

1
2
3
4
5
6
7
8
public class Authorizer implements RequestHandler<APIGatewayProxyRequestEvent, T> {

    public T handleRequest(APIGatewayProxyRequestEvent request, Context context) {
        Map<String, String> headers = request.getHeaders()
        String authorization = headers.get("Authorization");
        String jwt = authorization.substring("Bearer ".length());
    }
}

The API Gateway expects the Lambda Authorizer to return an object that can be serialised into a PolicyDocument (Json) like below. This document will be used by Amazon to determine which endpoints may be called by the caller.

1
2
3
4
5
6
7
8
9
10
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "execute-api:Invoke"
      "Effect": "Allow",
      "Resource": "arn:aws:execute-api:<region>:<account-id>:<api-id>/<stage>/<http-method>/<path>"
    }
  ]
}

Hold on a sec…

We were promised that we would enrich the request, not just return a policy that says whether it’s authorized or not.

You are right, Amazon allows us to return additional information using a context alongside the PolicyDocument this means that our actual response will look something like below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
  "principalId": "<account-id>",
  "policyDocument": {
    "Version": "2012-10-17",
    "Statement": [{
      "Action": "execute-api:Invoke"
      "Effect": "Allow",
      "Resource": "arn:aws:execute-api:<region>:<account-id>:<api-id>/<stage>/<http-method>/<path>"
    }]
  },
  "context": {
    "<key>": "<value>",
    ...
  }
}

Note: The Jackson Builder classes for constructing the PolicyDocument can be found in the github repository of this tutorial.

Alrighty! Let’s have a look at our finished Lambda Authorizer.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class Authorizer implements RequestHandler<APIGatewayProxyRequestEvent, AuthorizerResponse> {

    public AuthorizerResponse handleRequest(APIGatewayProxyRequestEvent request, Context context) {
        Map<String, String> headers = request.getHeaders();
	String authorization = headers.get("Authorization");
          
        String jwt = authorization.substring("Bearer ".length());

        Map<String, String> ctx = new HashMap<>();
        ctx.put("username", JwtUtils.extractUserName(jwt));

        APIGatewayProxyRequestEvent.ProxyRequestContext proxyContext = request.getRequestContext();
        APIGatewayProxyRequestEvent.RequestIdentity identity = proxyContext.getIdentity();

	String arn = String.format("arn:aws:execute-api:eu-west-1:%s:%s/%s/%s/%s",
            proxyContext.getAccountId(),
            proxyContext.getApiId(),
            proxyContext.getStage(),
            proxyContext.getHttpMethod(),
            "*");

	Statement statement = Statement.builder()
            .resource(arn)
            .build();

        PolicyDocument policyDocument = PolicyDocument.builder()
            .statements(
                Collections.singletonList(statement)
            ).build();

	return AuthorizerResponse.builder()
            .principalId(identity.getAccountId())
    	    .policyDocument(policyDocument)
            .context(ctx)
            .build();
    }
}

Using the context in the method integration

We now know how to return additional information in our Lambda Authorizer, but how do we actually use that information to enrich our request going to our backend service.

First of all, let’s attach our Lambda Authorizer to our API Gateway, see the how-to below for additional information: Configure Lambda Authorizer Using the API Gateway Console - Amazon API Gateway

The Amazon API Gateway allows for mappings which can contain expressions like method.request.path.id to receive a path parameter with the name id.

Navigating to our API Gateway and selecting our resource method we can navigate to the Integration Request where we can now start creating our username header.

The Integration Request is where we can instruct Amazon how to modify our request going to our backend service.

Alright, so we know how to map a certain request parameter to a header, but what about the Authorizer?

Remember the context we set in the Authorizer, that’s right it is now available in the global context scope where we can access it using the context.authorizer expression.

To access our username we can use: context.authorizer.username.

That’s all folks! Oh.. and don’t forget to deploy your API.

Source

References

Profile picture

Joery Vreijsen

Software developer • Java & AWS Guru • Github: vreijsen • Twitter: @JoeryVreijsen

At Kabisa, privacy is of the greatest importance. We think it is important that the data our visitors leave behind is handled with care. For example, you will not find tracking cookies from third parties such as Facebook, Hotjar or Hubspot on our website. Only cookies from Google and Vimeo are used in order to improve the user experience of our visitors. These cookies also ensure that relevant advertisements are displayed. Read more about the use of cookies in our privacy statement.