Photo by John Cameron on Unsplash

LAMBDA AUTHORIZER ON STEROIDS

Get More Out Of Lambda Authorizer

Use Lambda context to perform more than just authorization

7 min readJul 10, 2020

--

Lambda authorizers are used to control access to APIs published in AWS API Gateway. They help to implement custom authorization schemes that either use token based authentication strategies (like OIDC, SAML, etc.), or use one or more request parameters to establish the API caller’s identity.

There are essentially two types of Lambda authorizers:

  • Token authorizer: Here one has to declare a token source (like HTTP Authorization header) and Lambda authorizer runtime takes care of plumbing the token from token source and passing the same in the Lambda event. This token is available at event.authorizationToken, within the Lambda function
  • Request authorizer: In this case, the entire HTTP request (including headers, query string parameters, etc.) is passed as event to the Lambda function. The extraction of the authorization token from the request is left with the Lambda function implementation

In both these types, the ARN of API to which access is being requested, is passed along with the event at event.methodArn. This ARN is used to create an IAM policy document (a JSON object) with an effect of either ‘Allow’ or ‘Deny’. The Lambda authorizer validates either the incoming token or the set of request parameters representing the identity of caller and finally generates this IAM policy which is returned by the authorizer to API gateway. At this point, API gateway either grants access to the API in question or denies it.

So far so good. But what if we want to design the authorizer to generate new parameters that needs to be passed to API back-end? How do we pass these parameters from the authorizer to back-end? Let’s look at few such situations where this might be pertinent:

  • Generation of correlation ID (if not present in the request) by the authorizer
  • A custom request ID needs to be generated and passed to the backend as a header/query-string
  • A new security header (For example: Basic AuthN) needs to be passed to the backend

(In the following sections we’ll discuss the last scenario in detail, as an example)

Well, the trick is to add such generated parameters to response context of the authorizer. Doing so, makes sure that such parameters are available under context.authorizer object and is readily accessible in integration layer of the API which could then be passed onto the back-end as header or query-string using a body mapping template.

A typical IAM policy document generated by Lambda authorizer that intends to pass some new/transformed values to the back-end, could look something like this-

{
"principalId": "adrin",
"policyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Action": "execute-api:Invoke",
"Effect": "Allow"
}
]
},
"context": {
"basic_credentials": "Basic bW9zc2FkOkVpY2htYW5u"
}
}

Note that, there is a basic_credentials parameter under context. This value could be easily accessed in the Integration layer of API and could even be used to override a request header, path parameter or query-string before invoking the API back-end.

A Specific Scenario- Creating And Passing Basic AuthN Header To API Backend

With that introduction out of the way, let’s work with a hypothetical scenario where a RESTful API is published on AWS API gateway with a HTTP service as the API back-end. Each request to this API needs to carry a valid JWT bearer token as part of Authorization header and a client identifier in a custom HTTP header (say, x-client-id). Moreover, the back-end HTTP service requires Basic AuthN header to be passed.

Here’s what the Lambda authorizer, controlling access to the API is expected to do:

  1. Lookup client identifier (passed in x-client-id header) in Secrets Manager to fetch two pieces of information: credentials to be used with API back-end call in the form of Basic AuthN and a secret key to validate JWT token
  2. Validate Authorization Bearer (JWT) token using the secret key
  3. Prepare Basic AuthN header value from the credentials with Base64 encoding
  4. Finally create an IAM policy document with a response context to pass Basic AuthN header value to the back-end

(In case of an error or validation failure, the API caller will receive a HTTP 401 response code).

Figure-1: The hypothetical scenario

Check this GitHub link for a working version of Lambda request authorizer (written in Node.js) that validates an incoming JWT token and generates Basic AuthN header value and adds the same to response context.

Here are the important pieces of the solution-

Secret In SecretsManager

Create a secret in SecretsManager with a name same as the client ID and with a value that has the following structure:

{
"key": "<super secret key used for token validation>",
"credentials": {
"username": "<basic authn user>",
"password": "<basic authn password>"
}
}

Basically, we are storing JWT token validation key and credentials for Basic AuthN (used with the back-end call) in the secret object.

IAM Role For Lambda authorizer

Once the Lambda is deployed (Lambda deployment is pretty straightforward and not covered in this blog), it needs to be able to fetch secret value from a secret using the x-client-id as the secret name and also be able to write log events to CloudWatch. Here is the IAM policy assigned to the Lambda authorizer IAM role (replace AWS account id and Secret ARN with valid ones):

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"secretsmanager:GetSecretValue",
"logs:PutLogEvents"
],
"Resource": [
"arn:aws:logs:ap-south-1:123456789100:log-group:/aws/lambda/lambda_auth_clientid_jwt:*",
"arn:aws:secretsmanager:ap-south-1:123456789100:secret:cTrqY79Lrff1RdWtdtHM-Vd58Op"
]
},
{
"Effect": "Allow",
"Action": "logs:CreateLogGroup",
"Resource": "arn:aws:logs:ap-south-1:123456789100:*"
}
]
}

The REST API

With the secret created and Lambda function deployed along with appropriate permissions, its time to create the API in API Gateway which in turn, will be secured using the Lambda authorizer.

Here we will create a REST API named ‘echo’ with resource path ‘/echo’ and a simple GET operation. The integration type is HTTP and the endpoint URL points to a HTTP service endpoint that requires the Basic AuthN header to be passed.

Figure-2: Create a REST API with GET operation & HTTP integration type
Figure-3: The Method request/response & Integration request/response palette

Register Lambda As Authorizer

The Lambda needs to be registered as authorizer. In the API gateway web console, select the API and go to Authorizers section (appears in the left menu along with Resources and Staging). Here, create a new ‘request’ authorizer with two identity sources- x-client-id (header) and Authorization (header). Remember to enable caching and grant API gateway the permission to invoke the Lambda function. Here, the authorizer has been registered under the name: jwtauthorizer

Figure-4: Register the lambda function as a Request Authorizer

Once created, it is a good idea to test the Lambda authorizer. To do so, just click on the ‘Test’ button of the authorizer and provide values for x-client-id (equal to the secret name) and Authorization (a valid JWT token generated using the secret key). A response code of 200 and an effect of ‘Allow’ is a sign that the authorizer is working correctly.

Figure-5: Test the Lambda Authorizer

In case, you are wondering how to generate a valid JWT token, here’s a simple code snippet (in Node.js) using jsonwebtoken library: The code uses HMAC-SHA256 symmetric algorithm. However, asymmetric algorithms like RS256 could also be leveraged. In that case, the private key (of the key-pair) will have to be used to sign the JWT token.

const jwt = require('jsonwebtoken');
const key = "<fetch the secret key>";
var token = jwt.sign({ user: 'adrin',
email: 'adrin.mukherjee@gmail.com'},
key, { algorithm: 'HS256', expiresIn: '1h' });console.log(token);

Configure The API

There are two important steps that needs to be done with the API.

Firstly, we need to secure the API operation/s using Lambda authorizer. In order to do this, click on API Resources and select the operation/s (in this case a GET operation) and under Method Request > Settings, use the Authorization drop-down to select the freshly registered Lambda authorizer and save it.

Figure-6: Attach the Lambda Authorizer to the operation/s

Secondly, under Integration request, go to the HTTP Headers section and map the Authorization header to context.authorizer.basic_credentials under the ‘Mapped From’ column.

The same effect could be achieved if we go down to Mapping Templates section and create a new Content-Type of application/json and finally add a new mapping template. The template should contain the following lines:

#set($context.requestOverride.header.Authorization =                
$context.authorizer.basic_credentials)
$input.json("$")

The above steps essentially overrides the HTTP request header named Authorization with a value from the parameter named basic_credentials under context.authorizer. Remember, this value was set by Lambda authorizer in the response context while returning the IAM policy to API Gateway.

Figure-7: Create a new mapping template

Conclusion

That’s all folks. The scenario described above is just an example of how a Lambda authorizer can be leveraged to perform more than just authorization. In this particular case, Lambda authorizer generates value for Basic AuthN and passes this as a parameter to the API Integration layer, which in turn, passes the value as an Authorization header to API back-end.

This blog has been a lengthy one and if you have managed to reach this far, then thanks for your patience and enthusiasm. Happy learning…

--

--

Cosmic inhabitant from a pale blue dot in the Milky Way galaxy | Arik's father | Coldplay & Imagine Dragons fan | Solution Architect | Author | Shutterbug