Front-End Web & Mobile

Introducing Lambda authorization for AWS AppSync GraphQL APIs

This article was written by Brice Pellé, Principal Specialist Solutions Architect, AWS


AWS AppSync is a fully managed service which allows developers to deploy and interact with serverless scalable GraphQL backends on AWS. As an application data service, AppSync makes it easy to connect applications to multiple data sources using a single API. AppSync supports multiple authorization modes to cater to different access use cases:

  • API Keys (API_KEY)
  • Amazon Cognito User Pools (AMAZON_COGNITO_USER_POOLS)
  • OpenID Connect (OPENID_CONNECT)
  • AWS Identity and Access Management (AWS_IAM)
  • NEW: AWS Lambda (AWS_LAMBDA) – available today

These authorization modes can be used simultaneously in a single API, allowing different types of clients to access data. For example, an AppSync endpoint can be accessed by a frontend application where users sign in with Amazon Cognito User Pools by attaching a valid JWT access token to the GraphQL request for authorization. At the same time, a backend system powered by an AWS Lambda function can push updates to clients through the same API by assuming an AWS Identity and Access Management (IAM) role to authorize requests. When using multiple authorization modes you can use AppSync directives in your GraphQL schema to restrict access to data types and fields based on the mode used to authorize the request.

Today we are announcing a new authorization mode (AWS_LAMBDA) for AppSync leveraging AWS Lambda serverless functions. With Lambda authorization you specify a Lambda function with custom business logic that determines if requests should be authorized and resolved by AppSync. Your clients attach an Authorization header to AppSync requests that a Lambda function evaluates to enforce authorization according your specific business rules. Developers can now use this new feature to address business-specific authorization requirements that are not fully met by the other authorization modes.

Setting up AWS Lambda as authorization mode in AppSync

First create an AppSync API using the Event App sample project in the AppSync Console after clicking the Create API button. Next follow the steps:

  1. Go to the Settings section of your AppSync API from the left side menu.
  2. Select AWS Lambda as the default authorization mode for your API.
  3. Select the region for your Lambda function.
  4. Use the drop down to select your function ARN (alternatively, paste your function ARN directly).
  5. [Optional] Enter a TTL that specifies how long to cache the response from Lambda. The cache key is <api-id, authorization-token>. Using a TTL and caching your Lambda function’s response avoids repeated function invocations, and optimizes for cost.
  6. [Optional] Enter a regular expression to allow or block requests based on the Authorization header value. Setting up a regular expression filters out unexpected requests, avoid unnecessary function invocations, and optimizes for cost.

You can follow similar steps to configure AWS Lambda as an additional authorization mode. Note that you can only have a single AWS Lambda function configured to authorize your API.

How Lambda authorization works

Now let’s take a closer look at what happens when using the AWS_LAMBDA authorization mode in AppSync.

1. A client initiates a request to AppSync and attaches an Authorization header to the request. A request sent with curl would look like this:

$ curl -XPOST -H "Content-Type:application/graphql" -H "Authorization:custom-authorized" -d '{"query": "query { listEvents { items { id } } }"}' https://YOURAPPSYNCENDPOINT/graphql

Note that AppSync does not support unauthorized access. A request with no Authorization header is automatically denied.

2. AppSync evaluates the request:

  • If the optional regular expression (regex) to allow or block requests has been provided, AppSync evaluates it against the Authorization header string. If the regex does not match the request is automatically denied.
  • 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.

3. 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": {}
    }
}

4. The Lambda function executes its authorization business logic and returns a payload to AppSync:

{
    "isAuthorized": <true|false>,
    "resolverContext": {<JSON object, optional>},
    "deniedFields": [
        "<list of denied fields (ARNs or short names)>"
    ],
    "ttlOverride": <optional value in seconds that overrides the default ttl>
}

The isAuthorized field determines if the request should be authorized or not. The resolverContext field is a JSON object passed as $ctx.identity.resolverContext to the AppSync resolver. Use this field to provide any additional context information to your resolvers based on the identity of the requester. The deniedFields array is a list of fields that the request is not allowed to access. As you can see, the response from your Lambda function allows you to implement custom access control, deny access to specific fields, and securely pass user specific contextual information to your AppSync resolvers in order to make decisions based on the requester identity.

5. AppSync receives the Lambda authorization response and allows or denies access based on the isAuthorized field value.

6. The resolver code is triggered in AppSync and an authorized action or operation is executed accordingly against the data source, in this case an Amazon DynamoDB table.

Writing a Lambda function to authorize GraphQL API calls

With the above configuration, we can use the following Node.js Lambda function sample code to be executed when authorizing GraphQL API calls in AppSync:

exports.handler = async (event) => {
  console.log(`event >`, JSON.stringify(event, null, 2))
  const {
    authorizationToken,
    requestContext: { apiId, accountId },
  } = event
  const response = {
    isAuthorized: authorizationToken === 'custom-authorized',
    resolverContext: {
      userid: 'test-user-id',
      info: 'contextual information A',
      more_info: 'contextual information B',
    },
    deniedFields: [
      `arn:aws:appsync:${process.env.AWS_REGION}:${accountId}:apis/${apiId}/types/Event/fields/comments`,
      `Mutation.createEvent`,
    ],
    ttlOverride: 10,
  }
  console.log(`response >`, JSON.stringify(response, null, 2))
  return response
}

The function checks the authorization token and, if the value is custom-authorized, the request is allowed. The function also provides some data in the resolverContext object. This information is available in the AppSync resolver’s context identity object:

{
  "identity": {
    "resolverContext": {
      "userid": "test-user-id",
      "info": "contextual information A",
      "more_info": "contextual information B"
    }
  }
}

The functions denies access to the comments field on the Event type and the createEvent mutation. Note that we use two different formats to specify the denied fields, both are valid. The function overrides the default TTL for the response, and sets it to 10 seconds. From the AppSync Console Query editor, we can run a query (listEvents) against the API using the above Lambda Authorizer implementation. As expected, we can retrieve the list of events, but access to comments about an Event is not authorized.

Multi-Authorization with AWS Lambda

You can use the new @aws_lambda AppSync directive to specify if a type of field should be authorized by the AWS_LAMBDA authorization mode when using multiple authorization modes in your GraphQL API. In the GraphQL schema type definition below, both AWS_IAM and AWS_LAMBDA authorize access to the Event type, but only the AWS_LAMBDA mode can access the description field.

type Event @aws_iam @aws_lambda {
    id: ID!
    name: String
    where: String
    when: String
    description: String @aws_lambda
    comments(limit: Int, nextToken: String): CommentConnection
}

Leveraging Lambda authorization in your front-end web or mobile client

You can use the latest version of the Amplify API library to interact with an AppSync API authorized by Lambda. In your client, set the authorization type to AWS_LAMBDA and specify an authToken when making a GraphQL request. For example, in React you can use the following code:

import { useEffect } from 'react'
import Amplify, { API } from 'aws-amplify'

// your amplify configuration
const config = {
  aws_appsync_graphqlEndpoint: '<your appsync graphql endpoint>',
  aws_appsync_region: '<your appsync api region>',
  aws_appsync_authenticationType: 'AWS_LAMBDA',
}

Amplify.configure(config)

// query to list events
const listEvents = `query { listEvents { items { id } } }` 

// custom token function that returns authorization header value
const getAuthToken = async () => 'custom-authorized'

function App() {
  useEffect(() => {
    const fn = async () => {
      try {
        const authToken = await getAuthToken()
        const res = await API.graphql({ query: listEvents, authToken })
        console.log(res)
      } catch (error) {
        console.log(error)
      }
    }
    fn()
  }, [])

  return <div> My App </div>
}
export default App

Conclusion

The AWS_LAMBDA authorization mode adds a new way for developers to enforce security requirements for their AppSync APIs. For example, in B2B use cases, a business may want to provide unique and individual API keys to their customers. Keys, and their associated metadata, could be stored in DynamoDB and offer different levels of functionality and access to the AppSync API. Other customers may have custom or legacy OAuth systems that are not fully OIDC compliant, and need to directly interact with the system to implement authorization. Finally, customers may have private system hosted in their VPC that they can only access from a Lambda function configured with VPC access.

Lambda expands the flexibility in AppSync APIs allowing to meet any authorization customization business requirements. You can mix and match Lambda with all the other AppSync authorization modes in a single API to enhance security and protect your GraphQL data backends and clients.

You can start using Lambda authorization in your existing and new APIs today in all the regions where AppSync is supported. For more details, visit the AppSync documentation.