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.
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!