AWS AppSync Authorization using Lambda
AWS AppSync is a fully managed service that makes it easy to develop GraphQL APIs by handling the heavy lifting of securely connecting to data sources like AWS DynamoDB, Lambda, and more.
With AppSync, you create GraphQL APIs that your applications interact with over the internet. While the API endpoints are publicly reachable, they never allow unauthorized access.
I will create and deploy a lambda function in Node.js with AWS CDK. The AWS CDK is a tool that makes it easy to create your infrastructure through the code.
Prerequisites
- An AWS account
- AWS CLI configured (check out this link to see how to do it)
- NodeJS installed on your computer
Lambda function to authorize GraphQL API calls
Let's create the function to be executed when authorizing GraphQL API calls in AppSync:
mkdir appsync-lambda-authorizer
cd appsync-lambda-authorizer
cdk init --language typescript
The command will generate many files and install the necessary dependencies. At the root level of the CDK project , you should find the lib folder with the file called appsync-lambda-authorizer-stack.ts. Let's update the file as follows:
import { Stack, StackProps, Duration } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as path from 'path';
export class AppsyncLambdaAuthorizerStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const vpc = new ec2.Vpc(this, "thabolebeloVPC", {
maxAzs: 2,
natGateways: 1,
subnetConfiguration: [
{
name: 'private-subnet-1',
subnetType: ec2.SubnetType.PRIVATE_WITH_NAT,
cidrMask: 24,
},
{
name: 'public-subnet-1',
subnetType: ec2.SubnetType.PUBLIC,
cidrMask: 24,
},
],
});
const lambdaFunction = new lambda.Function(this, 'lambda-function', {
runtime: lambda.Runtime.NODEJS_14_X,
vpc,
vpcSubnets: {
subnetType: ec2.SubnetType.PRIVATE_WITH_NAT,
},
memorySize: 1024,
timeout: Duration.seconds(300),
handler: 'index.handler',
functionName: 'AppSyncAuthorizer',
code: lambda.Code.fromAsset(path.join(__dirname, '/../src/')),
});
}
}
The handler property indicates the entry file then the function to run inside this file. So the index.handler
can be broken down to:
- index: the file called
index.js
inside thesrc
directory. - handler: the function inside the
index.js
to execute.
Lets go over what the code will provision:
- A VPC with a
Public
and aPrivate
subnet group, this VPC will also provision 1 NAT Gateway which will allow our lambda to access the internet from aPrivate
subnet - A lambda function called
AppSyncAuthorizer
that will be placed in aPrivate
subnet in the VPC. The code for the lambda function is located in the src folder at the project root.
Let's add the code for the lambda function, name the file index.js
exports.handler = async (event) => {
console.log(`event >`, JSON.stringify(event, null, 2))
const {
authorizationToken,
requestContext: { apiId, accountId, requestId },
} = event
const response = {
isAuthorized: authorizationToken === 'thabolebelo',
ttlOverride: 10,
}
console.log(`API_ID = `, apiId)
console.log(`ACCOUNT_ID = `, accountId)
console.log(`REQUEST_ID = `, requestId)
console.log(`AUTH_TOKEN = `, authorizationToken)
return response
}
AppSync receives the Lambda authorization response and allows or denies access based on the isAuthorized
field value:
- The function checks the authorization token and, if the value is
thabolebelo
, the request is allowed. - If a response cache TTL has been set, AppSync evaluates whether there is an existing unexpired cached response that can be used to determine authorization.
Deploy the Lambda function
The first step to deploy the Lambda is to generate the CloudFormation template from the CDK code. This command will output the CloudFormation stack to be created in the console:
cdk synth
Once done, we can deploy our app:
cdk deploy
After a deployment, we can see that the lambda has been launched in a VPC, and is associated to private subnets only.
![](https://s3.amazonaws.com/thabolebelo.com/2022/03/Screenshot-2022-03-27-at-20.33.54.png)
Everything seems to be in place for our Lambda function launched in a VPC to have internet access.
Test the Lambda function
AppSync sends the request authorization event to the Lambda function for evaluation in the following format:
{
"authorizationToken": "ExampleAUTHtoken123123123",
"requestContext": {
"apiId": "aaaaaa123123123example123",
"accountId": "111122223333",
"requestId": "f4081827-1111-4444-5555-5cf4695f339f",
"queryString": "mutation CreateEvent {...}\n\nquery MyQuery {...}\n",
"operationName": "MyQuery",
"variables": {}
}
}
Head over to the deployed lambda function on AWS console in order to test the authorization:
![](https://s3.amazonaws.com/thabolebelo.com/2022/03/Screenshot-2022-03-27-at-20.37.47.png)
Open the lambda function then click on the test
button to configure the request's payload. A modal will popup:
![](https://s3.amazonaws.com/thabolebelo.com/2022/03/Screenshot-2022-03-27-at-20.40.15.png)
![](https://s3.amazonaws.com/thabolebelo.com/2022/03/Screenshot-2022-03-27-at-20.41.21.png)
The token our test event sends is ExampleAUTHtoken123123123
which will not approve the API call. We only get access when token is thabolebelo
. Let's test the function with the test event we just configured:
![](https://s3.amazonaws.com/thabolebelo.com/2022/03/Screenshot-2022-03-27-at-20.44.19.png)
We have successfully deployed a Lambda function with the CDK. Run cdk destroy
to destroy all the resources created in AWS. The code for this post is available on this repo.
I will build on this solution in a future post. We will create an Angular application that consumes a GraphQL API, we will authorize the client application using this solution!