NodeJS Lambda Authorizer for JWT Access Tokens

I recently created a Lambda Authorizer to secure AWS API Gateway endpoints with JSON Web Tokens (JWTs). I found that many tutorials exist for Lambda Authorizer creation, but I found a lack of examples for such a script in NodeJS.

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.

Diagram of NodeJS JWT Lambda Authorizer flow in a browser-based application Figure 1. Lambda Authorizer Role in AWS API Gateway Endpoint Access from Browser-Based Applications

I wrote the authorizer function with two npm dependencies, both maintained by Auth0: - jsonwebtoken - jwks-rsa

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!

comments powered by Disqus