Writing Custom Authorizers for AWS API Gateway
If you want to go serverless with your web app and you need an API running Lambda functions behind API Gateway on AWS is an excellent choice. The technology is mature, fast and cheap (if you know what you’re doing). You even get the first 1 million invocations for free each month. Once it’s set up you don’t have to provision servers and worry about over- or underprovisioning; you just pay per API call.
Getting everything correctly configured is no small feat, though.
What are Custom Authorizers?
You probably don’t want everyone to be able to call your REST-endpoint that fetches personal data from the database, the caller has to be authenticated. You could write this logic in the same Lambda function that handles the request but that could get messy very fast. Also you would have to duplicate this code for every endpoint and we don’t want to repeat ourselves.
There are two types of Custom Authorizers, token-based and request-based. They differ in the way they grant the caller permission to use the resource, either they get a token back or
High-level Overview
The AWS docs provide this useful overview of the dataflow:
{:class=“img-responsive”}
A simplistic round of steps
- Client sends a request to your API
- API Gateway extracts the token from the request and calls your custom authorizer with it
- Custom authorizer evaluates the token, generates a policy and sends it back to API Gateway.
- API Gateway evaluates the policy and calls your real lambda function that is registered for the API endpoint.
Hands-on
For our example we need three things:
- A lambda function that gets triggered when somebody calls our API Gateway endpoint.
- The actual API Gateway endpoint.
- A lambda function that serves as our custom authorizer.
Let’s get started.
The Lambda Function
Let’s log into AWS and create a new lambda function from scratch:
{:class=“img-responsive”}
We give it just a basic execution role (to be able to write to CloudWatch) and use Node 6.10 as the runtime (still waiting for Node 8 and async/await).
We leave the code as is:
{:class=“img-responsive”}
The API Gateway Endpoint
Now we go to API Gateway and set up a new API:
{:class=“img-responsive”}
In the next window, under actions
we create a new resource under /test
and enable CORS so we don’t run into trouble by calling the API from our own machine:
{:class=“img-responsive”}
After creating the resource we create a GET method (again under Actions
) select Lambda integration type with Lambda proxy integration and select our lambda function we created in step one (remember the region you created the lambda function in):
{:class=“img-responsive”}
Give API Gateway permission to execute your function in the next window and we’re good to go.
Deploying the API
Again under Actions
, we select deploy API, create a new stage and call it dev:
{:class=“img-responsive”}
Now we have a deployed API. If you open all tabs until the GET endpoint you should find your exact url:
{:class=“img-responsive”}
Let’s try calling our (totally unauthenticated) endpoint with cURL:
{:class=“img-responsive”}
It works! Now let’s go about authenticating it:
The Custom Authenticator
We create a new lambda function as seen in step 1:
{:class=“img-responsive”}
We leave the code as is for now.
The idea is that our function will:
- Get the token passed to it on the event object
- It does its authentication thing (validating the token)
- And then returns a policy document to API Gateway to explain if and what the caller is allowed to do.
To do step 1 we go back to API Gateway, select our API, then Authorizers
and Create Authorizer
{:class=“img-responsive”}
Here we can specify from which header API Gateway will extract the token and pass it to our authorizer. Usually the header should be called authorization
or something like it but just to show you that you can use whatever you want we call it bananaHeader
. Click create and go back to your GET /test method. Click on method request and under Authorization select your new authorizer:
{:class=“img-responsive”}
Click the little checkmark and under Actions
deploy the API again to stage dev
.
Now when we call our /test endpoint our authenticator lambda function will run first. As of right now it just returns “Hello from lambda” which will of course authenticate nothing. Let’s try calling the endpoint:
{:class=“img-responsive”}
As expected, we’re not getting through to our real endpoint. Let’s write the custom authentication:
The Custom Authentication Code
In a real API your authentication code can get quite complex, calling services like Auth0 to see if the token is valid and not yet expired but let’s keep it simple. The AWS docs have a great example for this. The token is a string and can either be ‘allow’, ‘deny’, ‘unauthorized’ or something else. Depending on which is the case they generate the corresponding policy document to tell API Gateway what the user is allowed to do.
Here’s the code:
// A simple TOKEN authorizer example to demonstrate how to use an authorization token
// to allow or deny a request. In this example, the caller named 'user' is allowed to invoke
// a request if the client-supplied token value is 'allow'. The caller is not allowed to invoke
// the request if the token value is 'deny'. If the token value is 'Unauthorized', the function
// returns the 'Unauthorized' error with an HTTP status code of 401. For any other token value,
// the authorizer returns an 'Invalid token' error.
exports.handler = function(event, context, callback) {
var token = event.authorizationToken
switch (token.toLowerCase()) {
case "allow":
callback(null, generatePolicy("user", "Allow", event.methodArn))
break
case "deny":
callback(null, generatePolicy("user", "Deny", event.methodArn))
break
case "unauthorized":
callback("Unauthorized") // Return a 401 Unauthorized response
break
default:
callback("Error: Invalid token")
}
}
// Help function to generate an IAM policy
var generatePolicy = function(principalId, effect, resource) {
var authResponse = {}
authResponse.principalId = principalId
if (effect && resource) {
var policyDocument = {}
policyDocument.Version = "2012-10-17"
policyDocument.Statement = []
var statementOne = {}
statementOne.Action = "execute-api:Invoke"
statementOne.Effect = effect
statementOne.Resource = resource
policyDocument.Statement[0] = statementOne
authResponse.policyDocument = policyDocument
}
// Optional output with custom properties of the String, Number or Boolean type.
authResponse.context = {
stringKey: "stringval",
numberKey: 123,
booleanKey: true,
}
return authResponse
}
Let’s use it in our lambda function and click save:
{:class=“img-responsive”}
The Finish
This should be it!
If we call our API and provide a header called bananaHeader with value “allow” we should get back our hello from lambda.
Let’s try it:
{:class=“img-responsive”}
Looks good!
Conclusion
Authenticating endpoints in AWS can be quite a lot of configuration but once it’s set up it’s a cheap and easy way to get it done. If your method of authentication changes, because you switch providers for example, just update your lambda code and you’re good to go.
Thanks for reading!