AWS Amplify: Cognito-Authorized GraphQL in a Lambda API (Part 1)

evan kirkiles
3 min readMar 7, 2022

--

In Amplify, when one needs to access DynamoDB tables from Lambda with Cognito-based authentication, i.e. user pool groups or IAM, the task is a bit tricky. This post details the creation of a Lambda layer which can facilitate such Cognito-based GraphQL requests with ease for Lambda functions built on top of it. In Part 2, we will construct the Lambda Function for cascade deletion, and in Part 3 we build the REST API and show client-side access.

Photo by Lorenzo Herrera on Unsplash

This post was heavily inspired by Paulo Pires’ post here, which shows how to build a layer for Lambda GraphQL requests using the Lambda function’s execution IAM policy––but not the caller’s.

You’re running happily along, having created a GraphQL schema in AWS Amplify with User Pool-based authentication, and are using @auth directives to limit access to that data (say, for example, only the authenticated owner has access to their data). You decide you want to add some more complex server-side (hypothetical in our serverless context) logic in a serverless REST API Lambda function that performs queries for a user and transforms & returns data based on the result, maybe manipulating other AWS resources.

But alas––after hours of trial and error, you’re soon stumped, for a simple execution role policy on the Lambda function does not have the limited Cognito-based owner scope you need, and instead has either access to all of the table data or none of it. How do we ensure that Lambda runs internal GraphQL queries as if it were the actual Cognito user calling the Lambda function? The answer is simple in theory, but requires some magic to get working.

In this tutorial, we’ll go through building a Lambda Layer with a client for IAM-based GraphQL requests that forwards through the Authorization header sent to the Lambda function itself.

Our first step is to initialize the Lambda Layer:

$ amplify add function
? Select which capability you want to add: Lambda layer (shared code & resource used across functions)
? Provide a name for your Lambda layer: layerLambdaGQL
? Choose the runtime that you want to use: NodeJS
? The current AWS account will always have access to this layer.

We also want to go to the layer’s lib folder to install the dependencies the layer will be using to perform the GraphQL requests:

$ cd amplify/backend/function/<layerName>/lib
$ npm install graphql --save
$ npm install graphql-tag --save
$ npm install url --save
$ npm install uuid --save

Now, let’s build our GraphQL client that Lambda functions in this layer will use. The logic here is that we initialize the client class with an Authorization header (of the IAM form Bearer XXXXX ) that is stored as a member variable. When we send out GraphQL requests from the client, we add that authorization token in as the Authorization header without the Bearer , which makes the Lambda function’s request operate as if it was sent by the original caller.

Save the following code for the layer’s GraphQL client at amplify/backend/function/<layerName>/opt/graphQLClient.js :

So what are we doing here? This is a fairly general-purpose GraphQL query that can perform normal queries and mutations as well as recursive paginated ones. On construction, the client saves the GraphQL endpoint from environment variables and the authorization Cognito JWT mentioned before. When we then call run with a query and variables, the HTTP request to our GraphQL endpoint will be signed with that authorization token and function as if it was called by the user referenced in that token.

That’s actually it for the Lambda layer. We simply define and export this GraphQL client to be made accessible at /opt/graphQLClient.js for Lambda functions in the layer. If you have a common set of queries and mutations in GQL, I like to put these in the Lambda Layer as well, under /opt/graphql/queries.js and /opt/graphql/mutations.js , respectively.

In Part 2, we will see how to then define a REST API with Lambda functions that use this Lambda layer’s GraphQL Client and sign some requests on behalf of the caller.

--

--