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