Using AWS AppSync Pipeline Resolvers for GraphQL Authorization
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.
- Connect to the Event datasource, get the event with
eventId
, retrieve the author - Compare the author of the Event with the currently logged in Cognito User to determine if mutation should be allowed
- If the Event author and logged in user are the same, proceed with the Note creation mutation
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