Poppulo’s public cloud of choice is AWS, which provides a variety of methods to authorize access via its API Gateway. As our platform is built on a hybrid cloud, we've found that a Lamba Authorizer provides the most flexible approach to authorizing user actions.
To give a simple definition for this mechanism, it is a function which intercepts an incoming request, makes a decision, and returns a binary (yes/no) access policy. This loose specification is powerful, as it means authorization decisions can be made using information from a variety of sources, not necessarily bound to AWS. This scenario is common in hybrid architectures, where a company may have on-premise services or databases with information which has not yet been migrated to the cloud.
An overview of the authorization workflow can be seen here, as defined by AWS’ documentation. The Lambda Auth function shown in the image identifies the Lambda Authorizer:

In Poppulo's case, a client would send a bearer token issued by our Identity Provider, Curity. This bearer token references information about a person or machine, and allows the Lambda Authorizer to make an access decision.
As Lambda Authorizers support validation from any source, it is an excellent choice for organizations who have delegated Identity Management to a cloud-agnostic provider.
How It Works
A Lambda Authorizer receives the caller's identity in a bearer token, such as a JSON Web Token (JWT) or an OAuth token. You specify the name of a header, usually Authorization
, used to validate your request. The value of this header is passed into your lambda in an event object, which looks like this:
{
"methodArn":"arn:aws:execute-api:{regionId}:{accountId}:{apiId}/{stage}/{httpVerb}/[{resource}/[{child-resources}]]",
"httpMethod": 'GET',
"headers": { Authorization: '{caller-supplied-token}' }
}
This is passed to a Lambda Authorizer, shown here as a simple TypeScript function:
exports.handler = async (event) => {
const token = event.headers.Authorization;
// Validate token based on properties
// relevant to your business.
const result = validate(token);
if (!result) {
return generatePolicy("Deny", event.methodArn);
}
return generatePolicy("Allow", event.methodArn);
};
In the Lambda Authorizer, an ALLOW
or DENY
decision is made and then passed back to the api gateway as a policy.
The output of this method is a policy document in JSON format:
{
"principalId": "user",
"policyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Action": "execute-api:Invoke",
"Effect": "Allow",
"Resource": "arn:aws:execute-api:us-east-1:account.../GET"
}
]
}
}
To clarify the terms in this document, here's a breakdown:
- Principal Id: The
principalId
is a required property on your Authorizer response. It represents the caller's identity. - Policy Document: The
policyDocument
is a required property and the core of the Authorizer response. You must return a valid IAM policy that allows access to the requested API Gateway resource. This requires defining an execution permission allowing the caller to perform a specific action, likeexecute-api:Invoke
, and specifying the resource on which they are allowed to perform this action.
This, then, is all you need. An input event, a function that validates a token, and a policy as output.
First Steps
Having adopted CDK a few months ago, we're now big believers. From quickly scaffolding a project, to easily adopting TypeScript for Node.js lambdas, CDK has proven itself again and again. It should come as no surprise that we highly recommend this approach.

Start by installing CDK globally:
npm install -g aws-cdk
Then, create a new directory for your Lambda Authorizer, and initialize a git repository:
mkdir lambda-authorizer
&& cd lambda-authorizer
&& git init .
Now we can create a simple CDK stack by initialising a project with a template – in this case, TypeScript:
cdk init app --language=typescript
Other language options are available too, depending on your preference :
Available templates:
* app: Template for a CDK Application
└─ cdk init app --language=[csharp|fsharp|java|javascript|python|typescript]
* lib: Template for a CDK Construct Library
└─ cdk init lib --language=typescript
* sample-app: Example CDK Application with some constructs
└─ cdk init sample-app --language=[csharp|fsharp|java|javascript|python|typescript]
This will generate a very simple project scaffold, containing the bare minimum required to run what AWS calls a "Stack":

Next, we'll create a new folder in this repository called lambda, move into this directory via the command line, and initialise a new npm project. The -y
flag here accepts all default config during creation:
mkdir lambda && cd lambda && npm init -y
Now we can create our TypeScript-based lambda, which CDK is configured to transpile automatically:
touch index.ts
In this file, add a handler, some validation logic, and a policy generator, similar to what we outlined earlier in this article:
exports.handler = async (event: any) => {
var token = event.headers.Authorization;
const result = await validate(token);
if (!result) {
return generatePolicy('Deny', event.methodArn);
}
return generatePolicy("Allow", event.methodArn);
};
async function validate(token: string): Promise<boolean> {
// Your validation logic here.
return new Promise((resolve, reject) => {
resolve(true);
});
}
interface AuthResponse {
principalId: string;
policyDocument: PolicyDocument;
}
interface PolicyDocument {
Version: string;
Statement: Statement[];
}
interface Statement {
Action: string;
Effect: string;
Resource: string;
}
function generatePolicy(effect: string, resource: string) {
const statement: Statement = {
Action: 'execute-api:Invoke',
Effect: effect,
Resource: resource,
};
const policyDocument: PolicyDocument = {
Version: '2012-10-17',
Statement: [statement],
};
const authResponse: AuthResponse = {
principalId: 'user',
policyDocument: policyDocument,
};
return authResponse;
}
Next, switch to the lib
folder, and install the aws-lambda
dependency:
npm install @aws-cdk/aws-lambda
Then, register your new lambda authorizer as part of the stack:
import * as cdk from '@aws-cdk/core';
import * as lambda from '@aws-cdk/aws-lambda';
export class LambdaAuthorizerStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Your new authorizer
const authorizer = new lambda.Function(this, 'index', {
runtime: lambda.Runtime.NODEJS_10_X,
code: lambda.Code.fromAsset('lambda', { exclude: ['*.ts'] }),
handler: 'index.handler',
});
}
}
Finally, build and deploy your stack to AWS.
npm run build && cdk deploy
This action will generate IAM Policy changes, to allow the execution of the lambda. Choose y
to proceed:

Once complete, you can see the newly registered and currently updating stack in your AWS console, under the Cloudformation "Stacks" section.
This area provides an overview of all configuration and resource details attached to this stack definition:

To ensure that our Lambda Authorizer was transpiled and uploaded correctly, click on the Resources tab. There you should see an item marked "Index" with some unique identifier beside it, and a type of AWS::Lambda::Function
.
These details identify our newly created Authorizer:

Click through the lambda's link, under the heading of Physical ID. This brings you to an AWS code environment, showing the transpiled contents of our Lambda Authorizer:

Congratulations, you've just built and deployed a type-safe, lightweight, version-controlled Lambda Authorizer to AWS! 🎉
Attaching It to a Gateway
Let's assume that our Lambda Authorizer will run in an account that we own and operate.
Go to the AWS API Gateway section in the AWS console, and choose an API that you wish to protect. Click on the Authorizers link, then click the blue button marked Create New Authorizer.

Choose a name, specify the Type as Lambda
and then give it a reference to the Lambda you created earlier. This identifier is the Physical ID found in the CloudFormation resource definition.
Leave the "Lambda Invoke Role" section blank, as it will be auto-generated by AWS. Next, set the "Lambda Event Payload" as Request
, the "Identity Source" as Authorization
, and disable "Authorization Caching".
Heads-Up!
The default caching policy for newly created lambdas can be a security hazard. If you manage your identity, authentication, and authorization separately from AWS's native features, you must disable this. If not, you risk having two sources of truth for a user's allowed access period.

Once you've configured the Authorizer, hit Create. You will be prompted to add a permission to your newly created Lambda Authorizer Function, so that it may be invoked by the API Gateway.
Click Grant & Create to continue.

The above step creates a resource policy for you automatically, but it can also configured from the console if required, using the following templated command:
aws lambda add-permission --function-name
"arn:aws:lambda:{region}:{ID}:function:{YourLambdaName}:live"
--source-arn "arn:aws:execute-api:{region}:{ID}:*"
--principal apigateway.amazonaws.com
--statement-id {YourIdentifier}
--action lambda:InvokeFunction
At this point, we've registered an API Gateway Authorizer, created an execution policy, and have all the individual components required to protect our APIs.
Finishing Touches
To assemble these components and protect an endpoint, we must start with a resource. Go to your API Gateway and select Resources. Identify a suitable resource to protect, then select an endpoint and its action, as seen here:

You should see a screen that looks similar to the following image. Identify the Method Request, which has the configuration Auth:NONE
, and click on its heading.

This is the Method Request settings screen, which allows you to configure the endpoint with additional properties. Select Authorization, and choose Lambda Authorizer from the drop-down list, and confirm your choice.
It should look like this:

Finally, deploy your protected endpoint by selecting Deploy API from the resource Resource Actions menu.

Taking It for a Spin
Identify your API URL (shown just after deployment) and, using your client of choice, set an empty Authorization
header, and make a GET
request to that endpoint. This simulates a request from an unauthenticated actor.
As you can see from the image below, it will fail with 401 Unauthorized
.

Repeat the above steps, but this time add a value for the Authorization
header. This is your expected "happy path", where a user has a valid token after authenticating.
The request should succeed, with status 200 OK
.
Success! 🎊

Takeaways
Lambda Authorizers are simple and scalable mechanisms for organisations looking to unify access to hybridised services through a cloud-based API.
For Poppulo, as we use a cloud-agnostic Identity Provider, AWS Lambda Authorizers ensure flexibility for the widest range of hybrid cloud configurations.
If you are on a similar path, we've found that the following recommendations have proven valuable:
- Using CDK for lambda projects.
- Using Request or Token type Lambda Authorizers, with bearer tokens.
- Disabling policy caching in Lambda Authorizers if your identity and token time-to-live values are managed separately.
In addition, many of the steps outlined here follow an infrastructure-as-code pattern, but some intentionally do not, to help guide newcomers to AWS. In production we define all the above steps programmatically, and recommend that you do, too.