NodeJS Lambda Authorizer for JWT Access Tokens

December 2018 · 2 minute read

Here’s a nice diagram I created that depicts the Authentication and Authorization process using a web or mobile app, AWS API Gateway, a Lambda Authorizer, and OAuth2-issued JWT access tokens.

A diagram showing how Lambda Authorizer operates at the AWS API Gateway

Lambda Authorizer Role in AWS API Gateway Endpoint Access from Web Applications

I wrote the authorizer function with two npm dependencies, both maintained by Auth0:

The jsonwebtoken package handles the logic behind token decoding, verification of the signature, checking for expiration, and checking for other options which you specify. Customize the verificationOptions variable to your liking (and make sure that you check the “audience” property to be sure that this token was issued for use at your resource server).

The jwks-rsa package handles the retrieval of your ID Provider’s signing keys. The code assumes that your ID Provider uses solely the RSA256 signing algorithm, but this can be changed in the verificationOptions algorithms parameter if it is not the case.

The code can be found below, and also at https://github.com/cconcannon/lambda-authorizer-jwt

const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');
const keyClient = jwksClient({
    cache: true,
    cacheMaxAge: 86400000, //value in ms
    rateLimit: true,
    jwksRequestsPerMinute: 10,
    strictSsl: true,
    jwksUri: process.env.JWKS_URI
})

const verificationOptions = {
    // verify claims, e.g.
    // "audience": "urn:audience"
    "algorithms": "RS256"
}

const allow = {
    "principalId": "user",
    "policyDocument": {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Action": "execute-api:Invoke",
                "Effect": "Allow",
                "Resource": process.env.RESOURCE
            }
        ]
    }
}

function getSigningKey (header = decoded.header, callback) {
    keyClient.getSigningKey(header.kid, function(err, key) {
        const signingKey = key.publicKey || key.rsaPublicKey;
        callback(null, signingKey);
    })
}

function extractTokenFromHeader(e) {
    if (e.authorizationToken && e.authorizationToken.split(' ')[0] === 'Bearer') {
        return e.authorizationToken.split(' ')[1];
    } else {
        return e.authorizationToken;
    }
}

function validateToken(token, callback) {
    jwt.verify(token, getSigningKey, verificationOptions, function (error) {
        if (error) {
            callback("Unauthorized")
        } else {
            callback(null, allow)
        }
    })
}

exports.handler = (event, context, callback) => {
    let token = extractTokenFromHeader(event) || '';
    validateToken(token, callback);
}

If you make changes to the code, you will need to run npm i in your root folder, then zip the index.js file and the node_modules/ folder to upload to Lambda.

Thanks for reading!