Add a Cognito Authorizer to API Gateway V2 in AWS CDK

avatar

Borislav Hadzhiev

Thu Apr 22 20214 min read

banner

Photo by Zoe Deal

Updated on Thu Apr 22 2021

Adding Cognito Authorizers to an API in AWS CDK #

In order to control access to our lambda functions we can make use of authorizers.

In this article we will look at a complete example of how we can protect our Lambda functions with an API Gateway (Cognito JWT) authorizer in a CDK provisioned application.

In order to attach a Cognito Authorizer to an API we have to create the authorizer, by using the HttpUserPoolAuthorizer construct and set the authorizer when creating the API route.

The code for this article is available on GitHub

Project Setup #

The code in the GitHub repository provisions:

  • an API Gateway
  • a Lambda function that only allows authorized user access
  • Cognito User pool and User pool client
  1. Clone the Github Repository
  2. Install the dependencies:
shell
npm install
  1. Create the CDK stack
shell
npx cdk deploy \ --outputs-file ./cdk-outputs.json

Creating Cognito Authorizers for an API using AWS CDK #

Let's focus on the code that's related to provisioning the Cognito Authorizer and attaching it to the API Route:

lib/cdk-starter-stack.ts
// ๐Ÿ‘‡ create the lambda that sits behind the authorizer const lambdaFunction = new NodejsFunction(this, 'my-function', { runtime: lambda.Runtime.NODEJS_14_X, handler: 'main', entry: path.join(__dirname, `/../src/protected-function/index.ts`), }); // ๐Ÿ‘‡ create the API const httpApi = new apiGateway.HttpApi(this, 'api', { apiName: `my-api`, }); // ๐Ÿ‘‡ create the Authorizer const authorizer = new apiGatewayAuthorizers.HttpUserPoolAuthorizer({ userPool, userPoolClient, identitySource: ['$request.header.Authorization'], }); // ๐Ÿ‘‡ set the Authorizer on the Route httpApi.addRoutes({ integration: new apiGatewayIntegrations.LambdaProxyIntegration({ handler: lambdaFunction, }), path: '/protected', authorizer, });

Let's go over what we did in the code snippet:

  1. We created a basic lambda function and an API
  2. We created an Authorizer where we provided the following parameters:
  • userPool - the user pool this authorizer will be associated with
  • userPoolClient - the user pool client, that will be used to authorize requests with the user pool
  • identitySource - the identity source, which requests authorization - in our case the JWT token will be passed in the Authorization HTTP header to authorize a user
  1. We added a route to the API and we set the authorizer property to the authorizer we created

The code for the Lambda function, that only allows for authorized access could be as simple as:

src/protected-function/index.ts
import {APIGatewayProxyEventV2, APIGatewayProxyResultV2} from 'aws-lambda'; export async function main( event: APIGatewayProxyEventV2, ): Promise<APIGatewayProxyResultV2> { console.log('This function can only be invoked by authorized users'); console.log('event', JSON.stringify(event, null, 2)); return {body: JSON.stringify({message: 'SUCCESS'}), statusCode: 200}; }

The function will only get invoked after the Authorizer has checked for the validity of the JWT token that was provided in the Authorization HTTP header.

Testing the Cognito JWT Authorizer #

Let's test if our lambda function is protected by the authorizer.

In order to test the flow we have to:

  1. create a Cognito User
  2. confirm the user so they can sign in
  3. log the user in to get an identity JWT token
  4. use the token to invoke our API endpoint which will call the function (if the token is valid).
You can find the userPoolId, userPoolClientId and apiUrl identifiers in the cdk-outputs.json file in the root directory of your project. Alternatively you can grab them using the AWS Console.
Make sure you don't confuse the User Pool id and the User Pool Client id, because the commands below use both.
  1. Register a user, using the aws cli
shell
aws cognito-idp sign-up \ --client-id YOUR_USER_POOL_CLIENT_ID \ --username "test@test.com" \ --password "password123"
  1. Confirm the user, so they can log in
shell
aws cognito-idp admin-confirm-sign-up \ --user-pool-id YOUR_USER_POOL_ID \ --username "test@test.com"

At this point if you look at your cognito user pool, you would see that the user is confirmed and ready to log in:

confirmed user

  1. Log the user in
shell
aws cognito-idp initiate-auth \ --auth-flow USER_PASSWORD_AUTH \ --auth-parameters \ USERNAME="test@test.com",PASSWORD="password123" \ --client-id YOUR_USER_POOL_CLIENT_ID

You will get a very verbose response due to the length of these tokens. We only care about the IdToken, so copy and paste it into a notepad, because we will need it when invoking the API.

  1. Hit our Api to test the Authorizer. Note: you can find the Api Url in the cdk-outputs.json file in the root directory, or by opening the API gateway service in the AWS Console.

First we will send an anonymous request, without providing the Authorization header. The expected behavior would be to get a 401 Unauthorized response:

shell
curl --location --request GET 'YOUR_API_URL/protected'

The response is:

api-response
{"message": "Unauthorized"}

At this point we know that our Api authorizer works, let's try to send along the Authorization header and test the happy path:

shell
curl --location --request GET 'YOUR_API_URL/protected' \ --header 'Authorization: YOUR_ID_TOKEN'

The response comes straight from our Lambda function:

api-response
{"message": "SUCCESS"}

At this point we know that our authorizer works and validates the Authorization header.

Cleanup #

To delete the provisioned resources, run the destroy command:

shell
npx cdk destroy

Discussion #

Cognito authorizers enable us to place our lambda functions behind API Gateway, which checks for the validity of the user's JWT token provided in the Authorization header.

Api authorizers can be of 3 types:

  • Lambda authorizers - you can provision a lambda function and based on the event permit/forbid a request to go through. However you have to write the logic yourself.
  • JWT authorizers - based on a JWT token's validity (most commonly passed in the Authorization http header), the authorizer automatically permits / stops a request to your lambda function.
  • Standard AWS IAM roles and policies - allow you to create custom roles and policies to control who can call your API.

In this article we used a JWT authorizer as it is the most intuitive and common way to protect lambda functions.

Further Reading #

Add me on LinkedIn

I'm a Web Developer with TypeScript, React.js, Node.js and AWS experience.

Let's connect on LinkedIn

Join my newsletter

I'll send you 1 email a week with links to all of the articles I've written that week

Buy Me A Coffee