AWS AppSync within Lambda

AWS AppSync within Lambda

How to call AppSync queries and mutations within AWS Lambda and Cognito Pools

I’m using AWS AppSync to build Floom. 90% of the time it’s great, but there are still some pretty glaring feature gaps. One of the biggest is not being able to use a private key to access GraphQL operations through AWSAppSyncClient (for example, when doing backend operations with Lambda). It seems like there should be some kind of out-of-the-box admin access that allows you to mutate and query the database without needing to authenticate using AWS Cognito.

If you’re using AppSync, you’ve probably configured your GraphQL API to use AWS Cognito pools (which is the default). Using Cognito pools is incredibly convenient when for controlling access for different users on the client side. It allows you to use the @auth decorator in your schema.graphql to define read (list, get) and write (create, update, delete) access points.

If this is the case, and you want to access the GraphQL operations on your own backend servers (or on AWS Lambda), you are basically limited to two options:

  1. You can create an admin user in your Cognito Pool and then use that user to get access credentials for the AWSAppSyncClient SDK.
  2. There is a workaround using AWS IAM roles which is outlined here. This involves creating authenticated and unauthenticated roles, and then creating an Amazon Cognito identity pool and linking the roles you just created to the identity pool. This will also involve changing the authentication mechanism within your client applications. You will no longer be able to use the @auth decorator within schema.graphql, unless you write your own resolvers.

Here, I’ll explain how to move forward with option #1. A bird’s eye view of the process looks like this:

  1. Set up an admin user in AWS Cognito
  2. Use the admin’s login credentials to get a session jwtToken from AWS Cognito
  3. Use this jwtToken to get an instance of AWSAppSyncClient
  4. Now you can use AppSync GraphQL queries and mutations just like you do on the front end.

Creating an Admin User

I create a user with username admin and add this user to a group that’s also called admin.

No alt text provided for this image

Once you’ve done this, make sure that this user will have access to the GraphQL operations you’ll need within your Lambda functions. For example, at Floom, we need to update a Transaction once the order has been fulfilled by the seller, so I add an @auth rule which allows users in the admin group complete read/write access to the Transaction model.

type Transaction
  @model
  @auth(rules: [
    {allow: groups, groups: ["admin"]},
	{allow: owner, mutations: [create], queries: [get, list]},
  ])
{
  id: ID!
  output: [ServiceOutputValue]
  // more fields here
}

Getting A Session Token in Lambda

To access AppSync’s GraphQL operations within Lambda, without needing to switch to AWS_IAM authentication, we’ll need to fetch session tokens using our admin user. We can do this through the CognitoIdentityServiceProvider module of the aws-sdk:

const aws = require('aws-sdk')
const cognitoidentityserviceprovider = new aws.CognitoIdentityServiceProvider({ apiVersion: '2016-04-18' })
const cognitoSP = new aws.CognitoIdentityServiceProvider(
  {region: YOUR_AWS_COGNITO_REGION}
)

// structured params for the adminInitiateAuth method
const initiateAuthParams = {
  AuthFlow:   "ADMIN_NO_SRP_AUTH",
  ClientId:   YOUR_COGNITO_CLIENT_ID,
  UserPoolId: YOUR_COGNITO_USER_POOL_ID,
  AuthParameters: {
    USERNAME: YOUR_ADMIN_USER_USERNAME,
    PASSWORD: YOUR_ADMIN_USER_PASSWORD,
  }
};

// Get an Id Token (JWT) for the user
function getCredentials() {
  return new Promise((resolve, reject) => {
    cognitoSP.adminInitiateAuth(initiateAuthParams, (authErr, authData) => {
      if (authErr) {
        console.log(authErr)
        reject(authErr)
      } else if (authData === null) {
        reject("Auth data is null")
      } else {
        console.log("Auth Successful")
        resolve(authData)
      }
    })
  })
}

Instantiating AWSAppSyncClient

Now that we have Cognito credentials, we can use them to instantiate an instance of the AWSAppSyncClient object, which will allow us to use the AppSync GraphQL queries and mutations.

const AWSAppSyncClient = require('aws-appsync').default;

let cred
let credExpirationDate = new Date('01-01-1970') // to keep track of if credentials are out of date

function getAppSyncClient() {
  return new AWSAppSyncClient({
    disableOffline: true,
    url: YOUR_GRAPHQL_ENDPOINT,
    region: YOUR_AWS_REGION,
    auth: {
      type: "AMAZON_COGNITO_USER_POOLS",
      jwtToken: async() => {
        // check if we already have credentials or if credentials are expiredif (!cred || credExpirationDate < new Date()) {// get new credentials
          cred = await getCredentials()
          // give ourselves a 10 minute leeway here
          credExpirationDate = new Date(+new Date() + (cred.AuthenticationResult.ExpiresIn - 600) * 1000)
        }
        return cred.AuthenticationResult.IdToken
      },
    },
  })
}

Using AWSAppSyncClient

When you amplify push your schema.graphql changes, Amplify will provide you with a queries.js and a mutations.js with GraphQL operations that look like this:

// from mutations.js
export const updateTransaction = `mutation UpdateTransaction($input: UpdateTransactionInput!) {
  updateTransaction(input: $input) {
    id// more fields
  }
}`;

// from queries.js
export const getTransaction = `query GetTransaction($id: ID!) {
  getTransaction(id: $id) {
    id
  }
}`;

You can either import this whole file to use in your Lambda function, or simply copy over the ones that you’ll need (remember to update them if you make changes to your schema.graphql.

const client = getAppSyncClient()
const gql = require('graphql-tag')
const { updateTransaction } = require('./mutations.js')

function updateTransactionOutput(id, output) {
  await client.hydrated()
  try {
    const transactionComplete = await client.mutate({
      mutation: gql`updateTransaction`,
      variables: {
        input: {
          id,
          output: output,
        }
      },
      fetchPolicy: 'no-cache',
    })
    return transactionComplete.data.updateTransaction
  } catch(err) {
    console.log(err)
  }
}

Now you can use AppSync’s GraphQL operations in a Lambda setting while still using Cognito Authentication (without AWS_IAM roles). When you call mutations, the Amplify GraphQL clients can even subscribe to these mutations using the subscriptions provided in subscriptions.js, like onUpdateTransaction in this case.


No alt text provided for this image


To view or add a comment, sign in

More articles by Rob Moore

Others also viewed

Explore content categories