Securing Serverless APIs: A Deep Dive into Lambda Authorizers and JWT
Protect your serverless APIs with robust authentication and authorization. This tutorial provides a step-by-step guide to implementing custom Lambda authorizers with JSON Web Tokens (JWT) for fine-grained access control.
When you build a public-facing serverless API with Amazon API Gateway and AWS Lambda, one of the first questions you need to answer is: "How do I secure it?" While API Gateway offers several built-in authentication mechanisms, including IAM and Cognito, sometimes you need a more flexible, custom solution.
This is where Lambda authorizers shine. A Lambda authorizer is a function that you write to control access to your API. It's a powerful pattern that lets you integrate with any token-based authentication provider, such as Auth0, Okta, or your own custom identity service.
This guide provides a deep dive into building a Lambda authorizer that validates JSON Web Tokens (JWT).
The Authentication Flow
The process is straightforward:
- A client authenticates with an identity provider (e.g., Auth0) and receives a JWT.
- The client makes a request to your API Gateway endpoint, including the JWT in the
Authorization
header (e.g.,Authorization: Bearer <your_jwt>
). - API Gateway invokes your Lambda authorizer function, passing it the token.
- Your Lambda authorizer validates the JWT's signature, expiration, and claims.
- If the token is valid, the authorizer returns an IAM policy that grants access to the requested endpoint. If it's invalid, it returns a policy that denies access.
- API Gateway uses this policy to either allow the request to proceed to your backend Lambda function or to return a
403 Forbidden
error.
Step 1: Building the Lambda Authorizer Function
Let's create the core of our security mechanism: the authorizer function itself. This Python function will be responsible for parsing and validating the JWT.
We'll use the popular PyJWT
library to handle the token validation. Make sure to include it in your function's deployment package.
import jwt
import os
# These should be configured securely, e.g., via environment variables or Secrets Manager
AUTH0_DOMAIN = os.environ.get('AUTH0_DOMAIN')
API_AUDIENCE = os.environ.get('API_AUDIENCE')
ALGORITHMS = ["RS256"]
def handler(event, context):
"""Validates a JWT and returns an IAM policy."""
try:
token = event['authorizationToken'].split(' ')[1] # Extract token from 'Bearer <token>'
# Fetch the JSON Web Key Set (JWKS) from your Auth0 domain
jwks_url = f'https://{AUTH0_DOMAIN}/.well-known/jwks.json'
jwks_client = jwt.PyJWKClient(jwks_url)
signing_key = jwks_client.get_signing_key_from_jwt(token).key
# Decode and validate the token
payload = jwt.decode(
token,
signing_key,
algorithms=ALGORITHMS,
audience=API_AUDIENCE,
issuer=f'https://{AUTH0_DOMAIN}/'
)
# If validation is successful, return an 'Allow' policy
policy = generate_policy(payload['sub'], 'Allow', event['methodArn'])
return policy
except Exception as e:
print(f"Authentication error: {e}")
# On any error, deny access
return generate_policy('user', 'Deny', event['methodArn'])
def generate_policy(principal_id, effect, resource):
"""Helper function to create an IAM policy document."""
return {
'principalId': principal_id,
'policyDocument': {
'Version': '2012-10-17',
'Statement': [{
'Action': 'execute-api:Invoke',
'Effect': effect,
'Resource': resource
}]
}
}
Key Points:
- JWKS: The authorizer dynamically fetches the public signing keys from the identity provider's JWKS endpoint. This is a security best practice that allows for automatic key rotation.
- Validation:
jwt.decode
is doing the heavy lifting. It verifies the token's signature, checks that it hasn't expired, and validates theaudience
andissuer
claims to ensure the token was intended for your API. - Policy Generation: The
generate_policy
function constructs the IAM policy that API Gateway needs to see. A successful validation returns anAllow
policy; any failure results in aDeny
policy.
Step 2: Configuring API Gateway
Now, let's hook this function up to API Gateway.
Create the Authorizer: In the API Gateway console, navigate to your API and select "Authorizers." Create a new Lambda authorizer.
- Lambda Function: Choose the authorizer function you just created.
- Lambda Event Payload: Select "Token."
- Token Source: Enter
Authorization
for the header name. - Enable caching to improve performance and reduce costs.
Attach the Authorizer to a Route: Go to the route you want to protect (e.g.,
GET /products
). In the "Method Request" settings, select your newly created Lambda authorizer as the authorization type.
Step 3: Testing the Secured Endpoint
Now, if you try to access your endpoint without a valid JWT, you'll receive a 403 Forbidden
response.
To access it successfully:
Get a valid JWT from your identity provider.
Make a request to the endpoint, including the token in the header:
curl --request GET \ --url https://<your-api-id>.execute-api.<region>.amazonaws.com/products \ --header 'Authorization: Bearer <your_jwt>'
If the token is valid, API Gateway will invoke your backend Lambda function and return the response.
Conclusion
Lambda authorizers are a powerful and flexible tool for securing your serverless APIs. By combining them with a standard like JWT, you can easily integrate with a wide range of identity providers and implement robust, custom authentication logic.
This pattern provides a clean separation of concerns, keeping your business logic free of authentication code and centralizing your security in a single, reusable function.