Auth0 authentication with the Serverless Framework
Introduction
Auth0 is a cloud-based identity and access management service that provides authentication and authorization solutions for applications. It allows developers to integrate user authentication into their applications with minimal effort. Auth0 supports a variety of authentication methods.
This project demonstrates two methods of integrating Auth0 authentication with the Serverless Framework for securing AWS Lambda APIs:
- JWT-based authentication using API Gateway
- Lambda-based custom authorizer
Both methods provide robust security for your serverless applications, but they have different use cases and trade-offs.
Prerequisites
- Auth0 Account: Create an account at Auth0.
- Node.js and NPM: Installed on your system.
- Serverless Framework: Installed globally using:
npm install -g serverless
JWT-based Authentication
This method uses API Gateway’s built-in JWT authorizer to validate tokens issued by Auth0.
Setup
- Create a new Auth0 application and API as described in the Auth0 documentation.
- Note your Auth0 domain and API audience.
- Update your
serverless.yml:
provider:
name: aws
runtime: python3.12
environment:
AUTH0_DOMAIN: ${env:AUTH0_DOMAIN}
AUTH0_AUDIENCE: ${env:AUTH0_AUDIENCE}
httpApi:
authorizers:
auth0Authorizer:
identitySource: $request.header.Authorization
issuerUrl: https://${self:provider.environment.AUTH0_DOMAIN}/
audience:
- ${self:provider.environment.AUTH0_AUDIENCE}
type: jwt
id: auth0Authorizer
functions:
hello:
handler: handlers/hello.handler
events:
- httpApi:
path: /hellojwtauth
method: get
authorizer: auth0Authorizer
Implementation
Create a Lambda function (handlers/hello.py) to handle the authenticated request:
def handler(event, context):
claims = event.get("requestContext", {}).get("authorizer", {}).get("jwt", {}).get("claims", {})
user_id = claims.get("sub", "Unknown User")
return {
"statusCode": 200,
"body": f"Hello, {user_id}!"
}
This setup configures an AWS API Gateway with JWT-based authentication using Auth0. The serverless.yml file sets up the JWT authorizer to validate tokens issued by Auth0 and protect the /hellojwtauth endpoint. The Lambda function extracts the user’s identity from the JWT claims and returns a personalized response.
Lambda-based Custom Authorizer
This method uses a custom Lambda function to validate tokens and provide fine-grained access control.
Setup
- Create a new Auth0 application and API as described in the Auth0 documentation.
- Note your Auth0 domain and API audience.
- Update your
serverless.yml:
provider:
name: aws
runtime: python3.12
environment:
AUTH0_DOMAIN: ${env:AUTH0_DOMAIN}
AUTH0_AUDIENCE: ${env:AUTH0_AUDIENCE}
httpApi:
authorizers:
customAuthorizer:
type: request
functionName: lamauthorizer
functions:
hellolambdaAuth:
handler: handlers/helloauth.handler
events:
- httpApi:
path: /hellolambdaauth
method: get
authorizer:
name: customAuthorizer
lamauthorizer:
handler: handlers/authorizer.handler
Implementation
- Create a custom authorizer function (
handlers/authorizer.py):
import jwt
from jwt import ExpiredSignatureError, InvalidTokenError
import os
import time
def handler(event, context):
authorization_header = event.get('headers', {}).get('authorization')
if not authorization_header:
return generate_policy('user', 'Deny', 'Unauthorized')
token = authorization_header.split(' ')[-1]
try:
decoded_token = jwt.decode(token, options={"verify_signature": False})
if decoded_token.get('iss') != f"https://{os.environ.get('AUTH0_DOMAIN')}/":
return generate_policy('user', 'Deny', 'InvalidIssuer')
if decoded_token.get('exp') < time.time():
return generate_policy('user', 'Deny', 'TokenExpired')
return generate_policy('user', 'Allow', 'AccessGranted')
except (ExpiredSignatureError, InvalidTokenError):
return generate_policy('user', 'Deny', 'InvalidToken')
def generate_policy(principal_id, effect, message):
return {
'principalId': principal_id,
'policyDocument': {
'Version': '2012-10-17',
'Statement': [{
'Action': 'execute-api:Invoke',
'Effect': effect,
'Resource': '*',
}]
},
'context': {
'message': message
}
}
- Create a Lambda function (
handlers/helloauth.py) to handle the authenticated request:
def handler(event, context):
claims = event.get("requestContext", {}).get("authorizer", {}).get("domainName", {})
user_id = claims.get("sub", "Unknown User")
return {
"statusCode": 200,
"body": "Hello from lambda auth!"
}
This setup uses a custom Lambda function as an authorizer to validate Auth0 JWT tokens and enforce access control in AWS API Gateway. The serverless.yml configures the lamauthorizer Lambda function to check the token’s validity, issuer, and expiration. The Lambda function extracts user identity from the token and grants or denies access based on these checks, while the helloauth function responds to authenticated requests.
Comparison and Best Practices
JWT-based Authentication (API Gateway)
Pros:
- Simpler setup and management
- Reduced latency (no additional Lambda invocation)
- Automatic token validation by API Gateway
Cons:
- Limited customization options
- Less flexibility for complex authorization logic
Best for:
- Simple authentication requirements
- High-performance APIs
- When you need only basic claims validation
Lambda-based Custom Authorizer
Pros:
- Highly customizable authorization logic
- Can integrate with other services or databases
- Supports complex access control policies
Cons:
- Increased latency due to additional Lambda invocation
- More complex setup and management
- Potential for higher costs due to additional Lambda executions
Best for:
- Complex authorization requirements
- Integration with external systems for authorization
- When you need fine-grained access control
Conclusion
Both JWT-based authentication and Lambda-based custom authorizers provide secure ways to protect your serverless APIs with Auth0. Choose the method that best fits your project’s requirements, considering factors such as complexity, performance, and customization needs.
For simple authentication scenarios, the JWT-based method using API Gateway is often sufficient and easier to implement. For more complex authorization logic or when you need to integrate with other systems, the Lambda-based custom authorizer provides greater flexibility and control.
Remember to always follow security best practices, keep your Auth0 configuration up to date, and regularly review and test your authentication and authorization mechanisms.
Get Started
To get started and try out the code:
1. Clone the Repository
Open your terminal and execute the following command:
git clone git@ssh.dev.azure.com:v3/7EDGEx/Backend/Backend
References
- Auth0 Documentation: Official documentation for setting up Auth0, including integrating JWT authentication and API security.
- AWS API Gateway - Lambda Authorizers: Guide to implementing Lambda authorizers in AWS API Gateway for custom authentication.
- Serverless Framework Documentation For Authorizers : Comprehensive guide for deploying and managing serverless applications using the Serverless Framework.
- JWT.io Introduction: Overview of JSON Web Tokens (JWT), including structure, usage, and security considerations.
- PyJWT Documentation: Python library documentation for encoding, decoding, and verifying JWT tokens.