API Key Authentication with API Gateway using AWS CDK

thomastaylor

Thomas Taylor

Posted on June 30, 2024

API Key Authentication with API Gateway using AWS CDK

API key authentication is a common method for securing APIs by controlling access to them. It's important to note that API keys are great for authentication, but further development should be made to ensure proper authorization at the business level. API keys do not ensure that the correct permissions are being enforced, only that the user has access to the API.

Regardless, let's get started! In this post, we're going to touch on a few services:

  1. API Gateway with Proxy Integration
  2. Lambda Authorizer
  3. AWS CDK

Get started

Conceptually, the flow of our application will look like this:

  1. Client makes a request to API Gateway with API key
  2. The lambda authorizer determines if the API key is valid
  3. If the API key is valid, the policy is generated and the request is allowed to pass through to the lambda function
  4. If the API key is invalid, the request is denied
  5. The lambda function is invoked and returns a response

Set up the CDK project

Firstly, let's create the CDK project. I will choose TypeScript as the language, but you can choose any language you prefer. Please refer to the AWS CDK hello world documentation for other supported languages.

cdk init --language typescript
Enter fullscreen mode Exit fullscreen mode

Next, let's install the necessary dependencies:

npm i
Enter fullscreen mode Exit fullscreen mode

In addition, install the @types/aws-lambda package:

npm i @types/aws-lambda
Enter fullscreen mode Exit fullscreen mode

Let's start by finding the primary stack file which is located under the lib directory. In my case, it's lib/api-key-gateway-stack.ts.

Edit the CDK stack

Luckily, in a few lines of code, we can spin up a full-featured API Gateway with a lambda handler using the AWS CDK.

import { Duration, Stack, StackProps } from "aws-cdk-lib";
import { Construct } from "constructs";
import { Runtime } from "aws-cdk-lib/aws-lambda";
import {
  LambdaRestApi,
  TokenAuthorizer,
  AuthorizationType,
} from "aws-cdk-lib/aws-apigateway";
import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs";

export class ApiKeyGatewayStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);
    const fn = new NodejsFunction(this, "server", {
      entry: "bin/server.ts",
      handler: "handler",
      runtime: Runtime.NODEJS_20_X,
      timeout: Duration.minutes(1),
    });
    const auth = new NodejsFunction(this, "auth", {
      entry: "bin/auth.ts",
      handler: "handler",
      runtime: Runtime.NODEJS_20_X,
      timeout: Duration.seconds(10),
    });
    const api = new LambdaRestApi(this, "api", {
      handler: fn,
      defaultMethodOptions: {
        authorizationType: AuthorizationType.CUSTOM,
        authorizer: new TokenAuthorizer(this, "authorizer", {
          handler: auth,
        }),
      },
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Let's break down the code:

  1. The first construct, NodejsFunction, is a node lambda function that will serve as our primary handler.
  2. The second construct, another NodejsFunction, is a lambda authorizer that will be used to validate the API key.
  3. The third construct, LambdaRestApi, is the API Gateway that includes the first construct wired as the proxy integration and the second construct as the authorizer.

Create the lambda handler

Located at bin/server.ts, we will create a simplistic lambda function that returns Hello, World!.

import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda";

export const handler = async (
  event: APIGatewayProxyEvent,
): Promise<APIGatewayProxyResult> => {
  return {
    statusCode: 200,
    body: JSON.stringify({ message: "Hello, World!" }),
  };
};
Enter fullscreen mode Exit fullscreen mode

Create the lambda authorizer

Next, let's create the lambda authorizer located at bin/auth.ts. This lambda function will be responsible for validating the API key.

To keep it simple, we will hardcode the API key to Bearer abc123.

import { APIGatewayTokenAuthorizerEvent, Handler } from "aws-lambda";

export const handler: Handler = async (
  event: APIGatewayTokenAuthorizerEvent,
) => {
  const effect = event.authorizationToken == "Bearer abc123" ? "Allow" : "Deny";
  return {
    principalId: "abc123",
    policyDocument: {
      Version: "2012-10-17",
      Statement: [
        {
          Action: "execute-api:Invoke",
          Effect: effect,
          Resource: [event.methodArn],
        },
      ],
    },
  };
};
Enter fullscreen mode Exit fullscreen mode

Deploy the stack

Now that we have our stack and lambda handlers setup, let's deploy the stack!

npx cdk deploy
Enter fullscreen mode Exit fullscreen mode

Once the deployment is complete, you should see the API Gateway endpoint as an output.

Do you wish to deploy these changes (y/n)? y
ApiKeyGatewayStack: deploying... [1/1]
ApiKeyGatewayStack: creating CloudFormation changeset...

 ✅  ApiKeyGatewayStack

✨  Deployment time: 45.34s

Outputs:
ApiKeyGatewayStack.apiEndpoint9349E63C = https://x2s65m7xyd.execute-api.us-east-1.amazonaws.com/prod/
Stack ARN:
arn:aws:cloudformation:us-east-1:123456789012:stack/ApiKeyGatewayStack/0ca225a0-3727-11ef-ae64-0affd17461c9
✨  Total time: 117.33s
Enter fullscreen mode Exit fullscreen mode

Test the API

Let's use curl to test the API without the API key.

curl https://<id>.execute-api.us-east-1.amazonaws.com/prod/
Enter fullscreen mode Exit fullscreen mode

Output:

{"message":"Unauthorized"}
Enter fullscreen mode Exit fullscreen mode

As expected, we received an unauthorized response. Now, let's test the API with the API key.

curl https://x2s65m7xyd.execute-api.us-east-1.amazonaws.com/prod/ \
    -H "Authorization: Bearer abc123"
Enter fullscreen mode Exit fullscreen mode

Output:

{"message":"Hello, World!"}
Enter fullscreen mode Exit fullscreen mode

Great! We have successfully created an API Gateway with a lambda authorizer using the AWS CDK. At this point, you may choose to extend the Lambda Authorizer to query another data source like DynamoDB that stores API keys.

Clean up

Lastly, let's clean up our AWS resources by destroying the stack:

npx cdk destroy
Enter fullscreen mode Exit fullscreen mode

That's it! You successfully created an API Gateway with a lambda authorizer using the AWS CDK.

💖 💪 🙅 🚩
thomastaylor
Thomas Taylor

Posted on June 30, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related