Auth0 Lambda Authorizer
This is a simple custom authorizer for AWS lambda.
Requirements
- jwks-rsa
Motivation
When writing FaaS applications on AWS lambda that
have an http
endpoint, we sometimes want to
secure these endpoints and only allow authenticated
and authorized users to access them. We use JWT
for
this and for the past FaaS projects we have just copy
pasted this custom authorizer across projects. Extracting
it into a library will give us the ability to share
improvements and bugs across projects without having
to copy paste the code around.
Usage
We need to first create a jwksClient
using jwks-rsa
.
const client = jwksClient({
cache: true,
jwksRequestsPerMinute: 10,
jwksUri: process.env.JWKS_URI,
rateLimit: true,
});
The JWKS_URI
should be fetched from the Auth0
dashboard.
Here we have added it as an environment variable.
We also need to decode the auth0 secret. The approach we normally
take is to save the PUBLIC_PEM
using kms
and then decrypt it,
and convert it to a Buffer.
//Given a function for decrypting a KMS string:
const encodedString = await getDecryptedKmsString(process.env.AUTH0_WEB_PUBLIC_PEM);
const auth0Secret = new Buffer(encodedSecret, "base64").toString();
Then we need to create a callback that will be executed once
we have decoded the token. This callback is also responsible
for returning control to the lambda environment by calling context.succeed
or context.fail
:
const authenticateCallback = (err, authResponse: AuthResponse) => {
if (err) {
if (!err) {
log.error("Failed to authenticate with an unhandled error", err);
context.fail("Unauthorized");
} else {
log.error("Recieved an unauthorized request", err);
context.fail("Unauthorized");
}
} else {
log.info({ msg: "Successfully authenticated a request" });
context.succeed(authResponse);
}
}
This callback will get either an error
or a response from our authenticate
function.
AuthResponse looks like this:
export interface AuthResponse {
policyDocument: PolicyDocument;
principalId: string;
context: any;
}
This includes a policyDocument
, the principalId
and the context
.
The context
is a map that will contain the payload
along with the scope.
A default policyDocument
is created that allows access to execute
any lambda. However, we might not always want to do this.
We can create our own policyDocument
if we want more fine grained control
by using the getPolicyDocument
function.
//given that we have imported `ramda` as R.
if (R.contains("admin", context.roles) || R.contains("premiumUser", context.roles)) {
const policyDocument = getPolicyDocument("Allow", [event.methodArn])
authResponse.policyDocument = policyDocument;
} else {
const policyDocument = getPolicyDocument("Deny", [event.methodArn])
authResponse.policyDocument = policyDocument;
}