Last updated: Jan 27, 2024
Reading timeยท5 min
To control access to our lambda functions, we can make use of authorizers.
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.
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 in the GitHub repository provisions:
npm install
npx aws-cdk deploy \ --outputs-file ./cdk-outputs.json
Let's focus on the code that's related to provisioning the Cognito Authorizer and attaching it to the API Route.
// ๐ create the lambda that sits behind the authorizer const lambdaFunction = new NodejsFunction(this, 'my-function', { runtime: lambda.Runtime.NODEJS_18_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( 'user-pool-authorizer', userPool, { userPoolClients: [userPoolClient], identitySource: ['$request.header.Authorization'], }, ); // ๐ set the Authorizer on the Route httpApi.addRoutes({ integration: new apiGatewayIntegrations.HttpLambdaIntegration( 'protected-fn-integration', lambdaFunction, ), path: '/protected', authorizer, });
Let's go over what we did in the code sample:
userPool
- the user pool this authorizer will be associated withuserPoolClients
- an array containing the user pool client(s) that will be
used to authorize requests with the user poolidentitySource
- the identity source, which requests authorization. In our
case the JWT token will be passed in the Authorization
HTTP header to
authorize a userauthorizer
property to the
authorizer we created.The code for the Lambda function that only allows for authorized access could be as simple as follows.
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.
Let's test if our lambda function is protected by the authorizer.
In order to test the flow we have to:
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.aws cognito-idp sign-up \ --client-id YOUR_USER_POOL_CLIENT_ID \ --username "test@test.com" \ --password "password123"
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 will see that the user is confirmed and ready to log in.
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.
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.
curl --location --request GET 'YOUR_API_URL/protected'
The response looks as follows.
{"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.
curl --location --request GET 'YOUR_API_URL/protected' \ --header 'Authorization: YOUR_ID_TOKEN'
The response comes straight from our Lambda function.
{"message": "SUCCESS"}
At this point we know that our authorizer works and validates the
Authorization
header.
To delete the provisioned resources, run the destroy
command:
npx aws-cdk destroy
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:
Authorization
HTTP header), the authorizer automatically permits/stops a
request to your lambda function.We used a JWT authorizer as it is the most intuitive and common way to protect lambda functions.
You can learn more about the related topics by checking out the following tutorials: