12.5 Authorizers: Lambda Authorizers and Cognito User Pool Authorizers
Right, let’s talk about the bouncer at the door of your API party: the Authorizer. You don’t want just anyone wandering in and helping themselves to the punch bowl (or worse, your precious database). API Gateway gives you a couple of primary tools to check IDs at the door: Lambda Authorizers and Cognito User Pool Authorizers. One is a custom-built, do-anything security guard you program yourself. The other is a highly trained, off-the-shelf specialist. Both get the job done, but your choice will define how much heavy lifting you’re signing up for.
Lambda Authorizer: Your Custom-Built Bouncer
A Lambda Authorizer (formerly known as a “custom authorizer”) is essentially you saying, “I’ll handle this myself, thanks.” You write a Lambda function that takes an incoming request, looks at something (usually a token in the Authorization header), and tells API Gateway whether to let the request through and, crucially, what to let through.
The beauty here is the absolute flexibility. That “something” can be a JWT, a custom API key, a magic string, or even the phase of the moon if you can code it. The function returns a policy document—that’s IAM speak for a very specific “yes/no, and here’s what they’re allowed to do” message.
Here’s the skeleton of what that Lambda function looks like. Notice we’re not just returning a simple true/false; we’re crafting an IAM policy on the fly.
exports.handler = async (event) => {
// 1. Extract the token from the header. This is where things often go wrong.
const token = event.authorizationToken?.replace('Bearer ', '');
if (!token) {
throw new Error('Unauthorized'); // API Gateway will translate this to a 401
}
// 2. Validate your token. This is your custom logic.
// You might verify a JWT, check a database, call another API, etc.
const user = await myCustomValidationFunction(token);
// 3. If it's invalid, DENY.
if (!user) {
throw new Error('Unauthorized');
}
// 4. If it's valid, generate an IAM policy allowing the request.
// The `methodArn` is the ARN of the API Gateway method they're trying to call.
const policy = {
principalId: user.id, // Any string you want to identify the user
policyDocument: {
Version: '2012-10-17',
Statement: [
{
Action: 'execute-api:Invoke',
Effect: 'Allow',
// This allows access to THIS specific method. For broader access, you could use a wildcard like 'arn:aws:execute-api:us-east-1:123456789012:abc123/prod/*/my-resource/*'
Resource: event.methodArn
}
]
},
// 5. (The Secret Sauce) Pass context to your backend integration!
context: {
userId: user.id,
role: user.role
// You can pass any key-value pairs here. They become available in your Lambda as `event.requestContext.authorizer`
}
};
return policy;
};
The killer feature is that context object. It lets you pass validated user information straight through to your backend Lambda. This means your actual business logic function doesn’t have to re-validate the token; it can just trust the event.requestContext.authorizer.userId and get on with its work. This is a huge architectural win.
The Pitfall: You own the complexity. Token validation, caching, key rotation, error handling—it’s all on you. If your validation logic is slow, it adds latency to every single API call. Always, always enable caching for your authorizer in API Gateway if your use case allows it. It’s a checkbox in the console and it will save your wallet and your latency graphs.
Cognito User Pool Authorizer: The Off-the-Shelf Specialist
If your users are authenticating through Amazon Cognito User Pools (which is a fantastic identity store), this is a no-brainer. You point your API Gateway method at your User Pool, and it handles the entire JWT validation process for you.
You configure the Authorizer in API Gateway to use your User Pool, and it automatically:
- Checks for the JWT in the
Authorizationheader. - Verifies its signature against the User Pool’s keys.
- Validates the expiration time, audience (
aud), and other standard JWT claims. - Automatically passes the validated claims from the JWT into the
requestContext.authorizeras context.
The code? There is none. That’s the point. You just set it up.
The Pitfall: It only works with Cognito User Pool JWTs. It’s a specialist, remember? If you need to accept tokens from another OIDC provider like Auth0 or Okta, you’re back to using a Lambda Authorizer to do the validation. Also, the configuration in the AWS console is… finicky. One wrong character in the audience field and it will silently reject valid tokens, leaving you to stare at CloudWatch logs wondering what you did wrong.
So, Which One Do You Choose?
- Use a Lambda Authorizer when you need ultimate flexibility. Your identity provider isn’t Cognito, you’re using a custom auth scheme, or you need to make complex, dynamic authorization decisions (e.g., “is this user allowed to access this specific database record?”).
- Use a Cognito Authorizer when your users are already in Cognito. It’s less code, less overhead, and it’s managed by AWS. You’d be a fool to reinvent this particular wheel.
Both are solid choices. The Lambda option is a swiss army knife; powerful but you might cut yourself. The Cognito option is a scalpel; precise and perfect for the job it was designed for. Choose your weapon.