Classic authorizers in ApiGateway are useful resources for managing simple authorizations for your users. In some cases, we need to add complex security features such as:
- Validating custom scopes.
- Checking specific claims before executing or accessing AWS resources.
For this, you need to use a lambda authorizer. In this article, we will cover how to deploy a custom lambda authorizer in a serverless application.
Project Creation
Comprehensive information about project creation can be found here in a dedicated post.
You now have a template.yml file in the sam-project folder, which we will edit.
Project Deployment
Open the template.yml
file and replace it with the following code:
1AWSTemplateFormatVersion: '2010-09-09' 2Transform: AWS::Serverless-2016-10-31 3Description: > 4 lambda-authorizer 5 6 Sample SAM Template for lambda-authorizer 7 8# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst 9Globals: 10 Function: 11 Timeout: 3 12 13Parameters: 14 Domain: 15 Type: String 16 Description: The custom domain part for OAuth 2.0 17 Default: "lambda-authorizer" 18 19Resources: 20 21 UserPool: 22 Type: "AWS::Cognito::UserPool" 23 Properties: 24 UserPoolName: userpool 25 MfaConfiguration: "OFF" 26 UserPoolAddOns: 27 AdvancedSecurityMode: "AUDIT" 28 29 UserPoolResourceServer: 30 Type: AWS::Cognito::UserPoolResourceServer 31 Properties: 32 UserPoolId: !Ref UserPool 33 Identifier: api.pixelo.fr 34 Name: api.pixelo.fr 35 Scopes: 36 - ScopeName: "read" 37 ScopeDescription: "Read" 38 39 UserPoolClient: 40 Type: AWS::Cognito::UserPoolClient 41 DependsOn: 42 - UserPoolResourceServer 43 Properties: 44 ClientName: AppClient 45 UserPoolId: !Ref UserPool 46 AllowedOAuthFlowsUserPoolClient: true 47 GenerateSecret: true 48 CallbackURLs: 49 - 'https://oauth.pstmn.io/v1/callback' 50 - 'https://oauth.pstmn.io/v1/browser-callback' 51 AllowedOAuthFlows: 52 - code 53 AllowedOAuthScopes: 54 - api.pixelo.fr/read 55 - email 56 - openid 57 - profile 58 SupportedIdentityProviders: 59 - COGNITO 60 61 UserPoolDomain: 62 Type: AWS::Cognito::UserPoolDomain 63 Properties: 64 Domain: !Ref Domain 65 UserPoolId: !Ref UserPool 66 67 68 ApiGateway: 69 Type: AWS::Serverless::Api 70 Properties: 71 StageName: dev 72 OpenApiVersion: '2.0' 73 Description: API supporting the application 74 Auth: 75 DefaultAuthorizer: MyLambdaTokenAuthorizer 76 Authorizers: 77 MyLambdaTokenAuthorizer: 78 FunctionArn: !GetAtt MyAuthFunction.Arn 79 80 HelloWorldFunction: 81 Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction 82 Properties: 83 FunctionName: HelloFct 84 CodeUri: hello-world/ 85 Handler: app.lambdaHandler 86 Runtime: nodejs20.x 87 Architectures: 88 - x86_64 89 Events: 90 HelloWorld: 91 Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api 92 Properties: 93 Path: /hello 94 Method: get 95 RestApiId: !Ref ApiGateway 96 Metadata: # Manage esbuild properties 97 BuildMethod: esbuild 98 BuildProperties: 99 Minify: true 100 Target: "es2020" 101 Sourcemap: true 102 EntryPoints: 103 - app.ts 104 105 MyAuthFunction: 106 Type: AWS::Serverless::Function 107 Properties: 108 CodeUri: hello-world/ 109 Handler: authorizer.handler 110 Runtime: nodejs18.x 111 Architectures: 112 - x86_64 113 Policies: 114 - Statement: 115 - Sid: LambdaLogGroup 116 Action: 117 - logs:CreateLogGroup 118 - logs:CreateLogStream 119 - logs:PutLogEvents 120 Effect: Allow 121 Resource: 122 - !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/* 123 Environment: 124 Variables: 125 NODE_OPTIONS: --enable-source-maps 126 CLIENT_ID: !Ref UserPoolClient 127 USER_POOL_ID: !Ref UserPool 128 129 Metadata: # Manage esbuild properties 130 BuildMethod: esbuild 131 BuildProperties: 132 Minify: true 133 Target: "es2020" 134 Sourcemap: true 135 EntryPoints: 136 - authorizer.ts 137 138Outputs: 139 # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function 140 # Find out more about other implicit resources you can reference within SAM 141 # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api 142 UserPoolId: 143 Value: !Ref UserPool 144 Description: The UserPool ID 145 UserPoolClientId: 146 Value: !Ref UserPoolClient 147 Description: The Client ID 148 UserPoolClientSecret: 149 Value: !GetAtt UserPoolClient.ClientSecret 150 Description: The Client Secret 151 UserPoolDomain: 152 Value: !Sub "https://${Domain}.auth.${AWS::Region}.amazoncognito.com" 153 Description: The OAuth 2.0 domain to use for user authentication 154 HelloWorldApi: 155 Description: "API Gateway endpoint URL for Prod stage for Hello World function" 156 Value: !Sub "https://${ApiGateway}.execute-api.${AWS::Region}.amazonaws.com/dev/hello/" 157 HelloWorldFunction: 158 Description: "Hello World Lambda Function ARN" 159 Value: !GetAtt HelloWorldFunction.Arn 160 HelloWorldFunctionIamRole: 161 Description: "Implicit IAM Role created for Hello World function" 162 Value: !GetAtt HelloWorldFunctionRole.Arn
With the following command, you will deploy theses AWS resources:
Lambda Authorizer
The lambda authorizer below is a nodejs function that uses the following libraries from the AWS SDK:
NPM installation :
1cd hello-word 2npm install aws-jwt-verify 3npm install aws-lambda -D
Add the file authorizer.ts
in the hello-world/
folder and include the following code:
1import { APIGatewayAuthorizerResult, APIGatewayTokenAuthorizerEvent } from 'aws-lambda'; 2import { CognitoJwtVerifier } from 'aws-jwt-verify'; 3 4export const handler = async (event: APIGatewayTokenAuthorizerEvent): Promise<APIGatewayAuthorizerResult> => { 5 const token = event.authorizationToken; 6 const methodArn = event.methodArn; 7 8 // Verifier that expects valid access tokens: 9 const verifier = CognitoJwtVerifier.create({ 10 userPoolId: `${process.env.USER_POOL_ID}`, 11 tokenUse: 'access', 12 clientId: `${process.env.CLIENT_ID}`, 13 }); 14 15 let success = false; 16 17 try { 18 const payload = await verifier.verify(token); 19 console.log('Token is valid. Payload:', payload); 20 success = true; 21 } catch { 22 console.log('Token not valid!'); 23 success = false; 24 } 25 26 // Return an authorization response indicating whether the request is authorized 27 return { 28 principalId: 'user', 29 policyDocument: { 30 Version: '2012-10-17', 31 Statement: [ 32 { 33 Action: 'execute-api:Invoke', 34 Effect: success ? 'Allow' : 'Deny', 35 Resource: methodArn, 36 }, 37 ], 38 }, 39 }; 40};
IAM
Ensure your credentials and IAM permissions include the following policies:
1{ 2 "Sid": "APIGateway", 3 "Effect": "Allow", 4 "Action": [ 5 "apigateway:*" 6 ], 7 "Resource": [ 8 "arn:aws:apigateway:*::*" 9 ] 10}, 11{ 12 "Sid": "Cognito", 13 "Effect": "Allow", 14 "Action": [ 15 "cognito-idp:*" 16 ], 17 "Resource": [ 18 "*" 19 ] 20}, 21{ 22 "Sid": "Lambda", 23 "Effect": "Allow", 24 "Action": [ 25 "lambda:*" 26 ], 27 "Resource": [ 28 "arn:aws:lambda:*:xxx:function:*", 29 "arn:aws:lambda:*:xxx:layer:*" 30 ] 31}
WARNING : These policies are not best practice and should be adjusted according to your security requirements.
Deploy
Deploy the project with the following commands:
1sam build 2sam deploy --stack-name lambda-authorizer
Test the lambda authorizer
To simplify testing, we will use Postman
Test unauthorized user
If you make an HTTP call to the HelloWorldApi URL available in the stack's Output parameters, you should get a 401 or 403 HTTP response with the body:
1{ 2 "message": "Unauthorized" 3}
Or, if an invalid token is presented:
1{ 2 "Message": "User is not authorized to access this resource with an explicit deny" 3}
This makes sense because you are not authenticated.
Create a user
To be authenticated, create a user in the Cognito UserPool.
Confirm it as verified and remember the password.
Configure OAuth 2.0 in Postman
Now, configure the OAuth 2.0 server in Postman to authenticate the previously created user.
Fill in the following fields in the Authorization tab
:
1- Auth Type : OAuth 2.0 2- Grant type : Authorization code 3- Auth URL : https://${Domain}.auth.eu-west-1.amazoncognito.com/login 4- Access Token URL : https://${Domain}.auth.eu-west-1.amazoncognito.com/oauth2/token 5- Client ID : ${UserPoolClientId} 6- Client Secret : ${UserPoolClientSecret} 7- Scope : api.pixelo.fr/read openid
Click on the Get New Access Token
button.
You should be redirected to the login form of the OAuth 2.0 server previously deployed.
Log in and change the password, after which you will be redirected to Postman with the access tokens.
Test authorized user
If you make another HTTP call to the HelloWorldApi lambda-authorizer, you should get a 200 HTTP response with the body:
1{ 2 "message": "hello world" 3}
Congratulations! You are now authorized to call the lambda function.
Summary
In this post, we learned how to deploy a lambda authorizer in ApiGateway using the serverless framework.
WARNING: Remember to clean up AWS resources
1sam delete