Adding a JWT Authorizer to AWS API Gateway Using Claudiajs

This tutorial is a step by step approach on adding a JWT (JSON Web Token) authorizer to an AWS API Gateway using Claudiajs. This tutorial assumes you have the latest Claudiajs CLI installed.
npm install claudia -g
What are AWS API Gateway Authorizers
An API Authorizer is a Lambda function that performs authentication and authorization on requests prior to AWS API Gateway execution. The Lambda returns an IAM policy that either permits or blocks the API requests that contain a particular authorization token. The returning IAM policy is then cached by the API Gateway so it can be later reused for up to 1 hour.
Getting Started
Create a new project and at the root of the project create two folders called /auth
and /API
respectively. You want to have the authorizer module separate from the API module because it contains authorization logic while the API module may contain database logic or even application logic. They have different concerns.
Create API Authorizer
In your /auth
directory create an auth module that decodes the JWT token. If the JWT decode operation is successful, return an IAM policy that gives the user access to the secure endpoints. If the decode operation fails, return "unauthorized" in the callback.
var jwt = require('jsonwebtoken');
exports.auth = function auth(event, context, callback) {
'use strict';
console.log('got event', event);
/// request will look like this
/*
* {
* "type":"TOKEN",
* "authorizationToken":"<Incoming bearer token>",
* "methodArn":"arn:aws:execute-api:<Region id>:<Account id>:<API id>/<Stage>/<Method>/<Resource path>"
* }
*/
try {
/// Decode the JWT Token
var decoded = jwt.verify(event.authorizationToken, 'keyboard cat');
} catch (e) {
///decoding the JWT token has failed. Do not authorize the request.
callback('Unauthorized');
return;
}
const userId = decoded.userId;
let methodArn = event.methodArn;
let tmp = methodArn.split(':'),
apiGatewayArnTmp = tmp[5].split('/'),
awsAccountId = tmp[4],
region = tmp[3],
restApiId = apiGatewayArnTmp[0],
stage = apiGatewayArnTmp[1];
//// create IAM Policy
const policy = {
'principalId': userId,
'policyDocument': {
'Version': '2012-10-17',
'Statement': [{
'Effect': 'Allow',
'Action': [
'execute-api:Invoke'
],
'Resource': [
'arn:aws:execute-api:' + region + ':' + awsAccountId + ':' + restApiId + '/' + stage + '/GET/self'
]
}]
}
};
if (event && event.authorizationToken && event.methodArn) {
//// return IAM Policy
callback(null, policy);
} else {
/// request is not authorized
callback('Unauthorized');
}
};
Install Dependencies
That auth module has only one dependency called jsonwebtoken
. The jsonwebtoken
module is responsible for encoding and decoding the JWT token.
npm install jsonwebtoken --save
Create the Auth Lambda
The following command deploys the auth module into an AWS Lambda in us-east-1 region.
claudia create --name auth --region us-east-1 --handler index.auth
Create API
In your /API
directory, create an API with /login
and /self
endpoints. The /login
endpoint should return an userId encoded as JWT token and the /self
endpoint should return the user that corresponds to that userId
. This would normally be some sort of a database query. But for demonstration purposes, I'm only using a conditional statement that checks for a hardcoded userId.
var ApiBuilder = require('claudia-api-builder'),
api = new ApiBuilder(),
jwt = require('jsonwebtoken');
module.exports = api;
/**
* This tells Claudiajs to link the API gateway to the JWT authorizer for validation.
*/
api.registerAuthorizer('jwtAuth', {
lambdaName: 'auth',
lambdaVersion: true
});
/**
* Login function.
* This is not a secure API endpoint.
* User login validation happens here. The username and password
* get posted to this endpoint and searched against the users table in the database.
* The userId of the corresponding user gets tokenized and returned.
*/
api.post('/login', function(request) {
return new Promise((resolve, reject) => {
let userId = 1;
let token = jwt.sign({
userId
}, 'keyboard cat'); /// signing the userId
resolve({
token
});
});
});
/**
* This is a secure endpoint. The authorizer will return the USERID
*/
api.get('/self', function(request) {
return new Promise((resolve, reject) => {
var userId = request.context.authorizerPrincipalId; ///userId
if (userId == 1) {
var user = {
name: "Milo",
twitter: "@notmilobejda",
userId: userId
};
resolve(user); /// return user
return;
}
reject('User does not exist');
});
}, {
customAuthorizer: 'jwtAuth'
}); /// This is very important. This tells Claudiajs that this method needs to be linked to the authorizer.
Install Dependencies
There are two dependencies here. The first one is the claudia-api-builder
which instructs Claudiajs to build API endpoints and the second one is jsonwebtoken
which encodes the userId to a JWT token.
npm install jsonwebtoken claudia-api-builder --save
Create the API Gateway
The following command creates the API gateway and deploys the API Lambda.
claudia create --name api --region us-east-1 --api-module index
Keep note of the API endpoint in that response.
If you check the AWS API Gateway console, the authorization for the /self
endpoint should be set as custom.
Testing Our JWT Authorizer
Open your terminal and make a request to the /login
endpoint to obtain a JWT token.
curl -X POST https://qkwv54bj7g.execute-api.us-east-1.amazonaws.com/latest/login
Your endpoint domain will be different from the one above so copying and pasting that curl command verbatim will not work as expected. You need to update the endpoint to match the API URL you received from when you created the API Gateway. Upon a successful API request, you should see a JWT token in your terminal.
Use that token to test the /self
endpoint.
curl -H "Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImlhdCI6MTUxMDMxMDAxN30.zN5rxk80ZHTUnWDpQbTCLFpueuBTqagxRfhAyyozF_Y" https://qkwv54bj7g.execute-api.us-east-1.amazonaws.com/latest/self
Now you have a JWT Authorizer for your AWS API Gateway. To take this further, you can expand on those API's by adding actual database logic. If you get lost at any step along the way, feel free to tweet at me. @notmilobejda