Justin Rahardjo

Lambda Custom Authorizer with Auth0

By Justin Rahardjo on Jan 18, 2021

Recently I integrated Auth0 with Lambda as an Authorizer to my API Gateway. Generally speaking it is quite straight forward, Auth0 has a great tutorial here. But I hit a couple snags that was a bit tricky to find information on so I thought I’d document some of that here.

Why Auth0?

Why did I choose Auth0 instead of Cognito? Basically, it came down to two things. Firstly, Auth0 has a much nicer pre-built login/sign up/logout pages. These are easily customizable and actually looks modern and works well in mobile devices. The other reason is that RBAC is much easier to manage in Auth0 compared to Cognito.

Adding and using Custom Authorizer in Serverless Framework

To create the custom authorizer, we first create a new Serverless service. Or you can just add it into an existing service, here are the items you want to add.

# auth-service
functions:
  tokenAuthorizer:
    handler: src/token-authorizer.main
    environment:
      AUTH0_JWKS_URI: REPLACE_WITH_REAL_VALUE
      AUTH0_AUDIENCE: REPLACE_WITH_REAL_VALUE
      AUTH0_ISSUER: REPLACE_WITH_REAL_VALUE
resources:
  Outputs:
    TokenAuthorizerArn:
      Value: !GetAtt [TokenAuthorizerLambdaFunction, Arn]

The output is required if you would like to use the Authorizer in a different service, so you can use it in another service. When we reference the TokenAuthorizerLambdaFunction this is the Serverless Framework’s convention where they PascalCase the function name and add LambdaFunction at the end.

# other-service
functions:
  testAuthorizer:
    handler: src/test-auth.main
      events:
        - http:
          path: test/auth
          method: get
          authorizer:
            arn: ${cf:auth-service-${self:provider.stage}.TokenAuthorizerArn}

Returning Custom Error Messages

Auth0 provides a great example of how the authorizer function should work here. So you can take that and adjust it to your use case.

The issue I ran into was returning custom error messages so I can provide some good messages of why the API call failed. The solution is to use API Gateway’s built-in responses.

So firstly, add those responses to API Gateway. I used the AWS CDK, but you can use any other method to add these in (e.g. CLI, CloudFormation or even in the AWS console).

// Expired Token Gateway Response
new GatewayResponse(this, 'ExpiredTokenGatewayResponse', {
  restApi: api,
  type: ResponseType.EXPIRED_TOKEN,
  statusCode: '401',
  templates: {
    'application/json': JSON.stringify({
      message: 'Provided token is expired',
    }),
  },
});
// Unauthenticated Gateway Response
new GatewayResponse(this, 'UnauthenticatedGatewayResponse', {
  restApi: api,
  type: ResponseType.UNAUTHORIZED,
  statusCode: '401',
  templates: {
    'application/json': JSON.stringify({
      message: '$context.error.message',
    }),
  },
});
// Unauthorized Gateway Response
new GatewayResponse(this, 'UnauthorizedGatewayResponse', {
  restApi: api,
  type: ResponseType.ACCESS_DENIED,
  statusCode: '403',
  templates: {
    'application/json': JSON.stringify({
      message: '$context.authorizer.errorMessage',
    }),
  },
});

The $context.authorizer.errorMessage (on line 30) is the value to take note of. Now when you deny your request from your Authorizer, you can return the following.

{
  policyDocument: REPLACE_WITH_DENY_POLICY_DOCUMENT,
  context: {
    errorMessage: 'Provided token is expired or not enough permissions'
  }
};

This will then return a 403 with the body containing

{ message: 'Provided token is expired or not enough permissions' }

when you deny a request from the Authorizer function.

That’s it! You should now be able to return some better messages instead of just Unauthorized which is the default message.


Hopefully that was helpful to some people. Let me know if there are other things that are confusing when setting things up and I can try help where I can, especially since AWS’ Documentation are a bit hard to navigate.