Securing Lambda with Cognito, API Gateway, Amplify, and the CDK

Dre May
5 min readJun 2, 2020

--

Getting Started

Getting started with AWS can often be daunting. Tying everything together in order to get off the ground isn’t always a simple task. Luckily the AWS CDK has entered the picture to make all of our lives easier. Using VSCode with typescript code completion makes scripting infrastructure easier than ever.

Use Case

I was recently asked by someone how they would go about creating a website with sign-in functionality. Initially I thought of trying to stitch together something by storing user data within DynamoDB; however, I faintly remembered from my AWS certification adventure that AWS Cognito was something to re-investigate. For the AWS exam Cognito was a quick set of facts to remember — now I was being asked to apply my certification in a real-world scenario. Looking down the side menu of Cognito in the AWS console was a lot to take in to say the least.

Goals

With the basic use case, I had the following technical goals in mind:

  • Create a User Pool by using the AWS CDK.
  • Create a test user within the User Pool by using the CDK.
  • Create a custom attribute for storing user specific data, such as are they a “gold member” status, or silver level member?
  • Create a lambda function that can be secured with Cognito.
  • Parse the Cognito JWT token in order to read the identity of the user.
  • Demonstrate calling the API utilizing AWS Amplify.

Getting Started

In order to get started, you will need the following tools:

  • Node with npm.
  • Typescript installed.
  • The AWS CDK installed
  • AWS Account with keys for programmatic access.

Setting up all of the foundation requirements is beyond the scope of this article; however, the following link from AWS covers this in greater detail: https://docs.aws.amazon.com/cdk/latest/guide/getting_started.html.

Source Listing

All of the code for this article can be found in the following GitHub repository: https://github.com/drewword/cognito-cdk-amplify

Setting Up A User Pool

Creating the User Pool is very straightforward through the following call:

const userPool = new UserPool(this, 'myUserPool', {
userPoolName: "demo-userpool",
selfSignUpEnabled: true,
customAttributes: {
member_status: new StringAttribute()
},
signInAliases: {
email: true,
phone: true,
username: true
}
});

This excerpt can be found within the following file in the Git repository: https://github.com/drewword/cognito-cdk-amplify/blob/master/lib/cognito-sample1-stack.ts

This is where I created a custom attribute called member_status, which would fulfill the requirement to add custom data for each individual user.

Adding a User Pool Client

A user pool client defines how you interact from the outside world with Cognito. This is where I defined a “web-only” type flow using an Oauth Implicit Code flow, as noted below:

const userPoolClient = userPool.addClient('app-client', 
{
oAuth: {
flows : {
implicitCodeGrant: true
},
scopes: [OAuthScope.OPENID],
callbackUrls: ['https://www.google.com']
}
}
);

The callback here would typically be your website; I am simply using google as a placeholder.

Creating a Test User

Cognito offers methods to create users through a variety of methods. I wanted to have a test user created with the CDK stack itself. This can be achieved by using an AwsCustomResource. By using an AwsCustomResource, any set of AWS commands can be called when the CDK stack is created.

Within the onCreate of the AwsCustomResource, the Cognito AdminCreateUser call is used, adding in the user with a “gold_member_status”. (see below depiction)

For the full listing of this custom resource, please refer to the GitHub link here: https://github.com/drewword/cognito-cdk-amplify/blob/master/lib/cognito-sample1-stack.ts

Creating the Lambda Function

Deploying a Lambda function with the CDK is rather straightforward. In the example, the code for the Lambda is extracted from the folder hello-lambda.

const helloWorldFunction = new Function(this,
'helloWorldFunction', {
code: new AssetCode('hello-lambda'),
handler: 'helloworld.handler',
runtime: Runtime.NODEJS_12_X
});

Setting up the Authorizer

Below is an outline of creation of the authorizer, and attaching the authorizer to the GET method for ApiGateway.

Accessing the Cognito Token within Lambda

When the lambda is called, we really want to answer the basic question of “Who is calling us?”. We know that ApiGateway has authenticated the caller; however, we want to know details about the user. This could be for various levels of roles, their email address, etc.

In order to access this information, simply look within the headers.

export const handler = 
async (event: any = {}): Promise<any> => {
console.log("Starting hello handler.");
const jwtHeaders = event.headers['Authorization'];

This is located in the hello-lambda folder within the helloworld.ts file. Decoding the JWT can be done with the jsonwebtoken library. Note, here I simply decode the token. In a production system, you would want to use the verify call of the library.

const decoded = jwt.decode(jwtHeaders);
const memberStatus = decoded["custom:member_status"];

The Amplify Test Client

In order to test the user creation, you can access the AWS hosted UI for authentication, or simply use the AWS Amplify client. For ease of use, within the git repository, look within the amplify-test-client folder.

The amplify test client performs the following:

  • Calls the authenticate API.
  • Responds to the initial challenge with an updated password.
  • Calls the Lambda API after authentication.

In order to call the amplify test client, use the CDK deploy output variables with the following command:

node ./amplify-client-test/index.js [USERPOOLID] [CLIENTID] [TESTURL]

Expected Test Output

After running cdk deploy, and then running the amplify test client, you should receive results similar to below. Note that the Cognito custom attribute of member_status is returned from the Lambda.

Conclusion

There was a lot to cover in this article to receive an end-to-end call from Amplify to ApiGateway, and returning with Cognito attributes. Please check out the source code on GitHub for full detail: https://github.com/drewword/cognito-cdk-amplify

--

--

Dre May
Dre May

Written by Dre May

I love writing about all technology including JavaScript, Java, Swift, and Kotlin.

No responses yet