Robbert•rocks

Adding basic auth to Api gateway using CDK and TypeScript.

Recently I had to implement basic authentication in a serverless project. AWS doesn't have a Basic Authentication authorizer, so I had to make one myself. In this article, I am going to explain how I implemented basic authentication in API Gateway in AWS using TypeScript and CDK.

What is basic authentication?

Basic Authentication works by base64-encoding credentials and sent that to the server using an Authentication header.

For example like this

curl \
  --header "Content-Type: application/json" \
  --header "Authorization: Basic dXNlcjpwYXNz" \
  --request GET https://example.com

As you can see this basicly sends the username an password as plain text over the wire, so this shouldn't be used without SSL.

How did I do it?

In order to add basic authentication to API Gateway we need make a custom autherizer. This is just a lambda function which returns a policy.

The handler looks like this.

export const authorizer: APIGatewayTokenAuthorizerHandler = async (event) => {
    const token = event.authorizationToken;
    const effect = 'Deny';
 
    if (compareTokenWithCredentials(token, "user", "pass")) {
        effect = 'Allow';
    }
 
    return {
        principalId: 'user',
        policyDocument: {
            Version: '2012-10-17',
            Statement: [
                {
                    Action: 'execute-api:Invoke',
                    Effect: effect,
                    Resource: '*',
                },
            ],
        },
    }
};
 
export default authorizer;

This lambda will return a Allow or Deny policy for all resources.

At first I used Resource: event.methodArn and this worked fine untill I added more endpoints. This is because Apigateway caches the result of the authorizer lamba based on the Authorization header. So all endpoints will get the same policy, with the wrongly cached resource.

For more information on this I recommend reading this article from Alex de Brie

Because Basic Authentication used base64 encoded string I had to find a function to create a that string. In your browser you can use the function btoa for this. In node this function doesn't exist, so I created my own.

const btoa = (str: string) => Buffer.from(str).toString('base64');

I also needed a function to compare the Basic Authenticaton token with some predetermined values. That one looked like this.

const compareTokenWithCredentials = (token: string, user: string, pass: string) => token === `Basic ${btoa(`${user}:${pass}`)}`;

Configure your stack

Now the handler is done. All that is left to do is add it to CDK.

const authorizerFn = new NodejsFunction(this, 'BasicAuthAuthorizer', {
  entry: 'src/authorizer/handler/authorizer.ts',
  handler: 'authorizer',
});
 
const authorizer = new TokenAuthorizer(this, 'CustomBasicAuthAuthorizer', {
  handler: authorizerFn,
  identitySource: 'method.request.header.Authorization',
});
 
const api = new RestApi(this, 'api');
 
api.root.resourceForPath('some/route')
  .addMethod('GET', new LambdaIntegration(someRouteFn), { authorizer });

And that's it, you now have send a Basic Authentication header with your call in order to access the endpoints. Beware you have to add the { authorizer } to all endpoints.