Using AWS AppSync Pipeline Resolvers for GraphQL Authorization

Craig Huber
3 min readNov 23, 2018

--

Recently, I decided to plunge head first into the world of GraphQL to create a backend API for a mobile app I’m developing. I briefly looked at a few solutions such as Hasura but ultimately ended up using AWS AppSync.

In just a few minutes using AWS AppSync you can have a fully hosted GraphQL environment that’s scalable, affordable and highly available.

However, once you start getting into more advanced use cases with AppSync you may discover some pain points. One of such pain points is being able to do fine grained access control with multiple data sources.

Luckily, yesterday AWS addressed this problem with the announcement of Pipeline Resolvers. After reading the announcement I tried to find more how-to articles on how to use them but found nothing. So this is my attempt to explain how to use them.

The Problem

AppSync has the concept of a resolver which you use to request and respond to requests for data from a single data source (such as DynamoDB). But, there are times where you want to perform some logic before writing or fetching data from your data source. In my case I have 2 tables, an Event table and a Note table in DynamoDB. Every Event has some notes associated which is represented in the below AppSync Schema

type Note {
eventId: ID!
noteId: ID!
content: String
author: String

}

input CreateNoteInput {
eventId: ID!
noteId: String!
content: String
}

type Event {
id: ID!
name: String
author: String
notes: [Note]
}

The difficultly I was facing was ensuring only the author of the Event could create Notes on the event. Since the Note and the Event are in separate DyanmoDB tables I needed a way to check the author of an Event before allowing the Note to be created. Normally this can be achieved using “Nested Resolvers” and has been explained in this article. Nested resolvers can be a bit cumbersome and luckily with Pipeline Resolvers you may not need to use them.

The Solution: Using Pipeline Resolvers

Pipeline Resolvers allow you to attach different functions to data sources and have those functions execute in a “pipeline”. On my createNote mutation I will attach a pipeline resolver to run the following logic.

  1. Connect to the Event datasource, get the event with eventId , retrieve the author
  2. Compare the author of the Event with the currently logged in Cognito User to determine if mutation should be allowed
  3. If the Event author and logged in user are the same, proceed with the Note creation mutation
Resolver Pipeline

Creating the Pipeline

  • Before Mapping Template:
{}

Function: isEventAuthor

Datasource: EventTable

  • Request Mapping Template
{
"operation": "GetItem",
"key": {
"id": $util.dynamodb.toDynamoDBJson($ctx.args.input.eventId),
}
}
  • Response Mapping Template

This is where my logic to compare author with logged in user will execute

## Raise a GraphQL field error in case of a datasource invocation error
#if($ctx.error)
$util.error($ctx.error.message, $ctx.error.type)
#end
## Check if user is author
#if($ctx.result.author != $ctx.identity.username)
$util.error("$ctx.identity.username is not the author of this event.")
#end
## Pass back the result from DynamoDB. **
$util.toJson($ctx.result)

Function: createNoteMutation

Datasource: NoteTable

  • Request Mapping Template

This is where the new Note is actually created.

#set( $attribs = $util.dynamodb.toMapValues($ctx.args.input) )
#set( $attribs.author = $util.dynamodb.toDynamoDB($ctx.identity.username))
#set( $attribs.created = $util.dynamodb.toDynamoDB($util.time.nowFormatted("yyyy-MM-dd HH:mm:ssZ")))
{
"operation" : "PutItem",
"key" : {
## If object "id" should come from GraphQL arguments, change to $util.dynamodb.toDynamoDBJson($ctx.args.id)
"id": $util.dynamodb.toDynamoDBJson($util.autoId()),
},
"attributeValues" : $util.toJson($attribs)
}
  • Response Mapping Template
## Raise a GraphQL field error in case of a datasource invocation error
#if($ctx.error)
$util.error($ctx.error.message, $ctx.error.type)
#end
## Pass back the result from DynamoDB. **
$util.toJson($ctx.result)

End

Using this pipeline we now have a fully functioning pipeline that controls authorization for data from different sources. In this case, only authors of an Event will be able to create Notes. However, we could extend the isAuthor function and reuse it in other pipelines. I hope you find this useful when using AWS Appsync

--

--

Craig Huber

Craig Huber is a Senior Devops Engineer where he focuses on building cloud infrastructure, automation, containers and helping teams deploy their applications