AWS AppSync
AWS AppSync Developer Guide


This is prerelease documentation for a service in preview release. It is subject to change.

This section describes options for configuring security and data protection for your applications.

There are three ways you can authorize applications to interact with your AWS AppSync GraphQL API. You specify which authorization type you use by specifying one of the following authorization type values in your AWS AppSync API or CLI call:


    For using API keys.


    For using AWS Identity and Access Management (IAM) permissions.


    For using an Amazon Cognito user pool.

API_KEY Authorization

Unauthenticated APIs require more strict throttling than authenticated APIs. One way to control throttling for unauthenticated GraphQL endpoints is through the use of API keys. An API key is a hard-coded value in your application that is generated by the AWS AppSync service when you create an unauthenticated GraphQL endpoint. You can rotate API keys from the console, from the CLI, or from the AWS AppSync API Reference.

API keys are configurable for upto 365 days, and you can extend an existing expiration date for upto another 365 days from that day. API Keys are recommended for development purposes or use cases where it's safe to expose a public API.

On the client, the API key is specified by the header x-api-key.

For example, if your API_KEY is 'ABC123', you can send a GraphQL query via curl as follows:

$ curl -XPOST -H "Content-Type:application/graphql" -H "x-api-key:ABC123" -d '{ "query": "query { movies { id } }" }' http://YOURAPPSYNCENDPOINT/graphql

AWS_IAM Authorization

This authorization type enforces the AWS Signature Version 4 Signing Process on the GraphQL API. You can associate Identity and Access Management (IAM) access policies with this authorization type. Your application can leverage this association by using an access key (which consists of an access key ID and secret access key) or by using short-lived, temporary credentials provided by Amazon Cognito Federated Identities.

If you want a role that has access to perform all data operations:

{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "appsync:GraphQL" ], "Resource": [ "arn:aws:appsync:us-west-2:123456789012:apis/YourGraphQLApiId/*" ] } ] }

You can find YourGraphQLApiId from the main API listing page in the AppSync console, directly under the name of your API. Alternatively you can retrieve it with the CLI: aws appsync list-graphql-apis

If you want to restrict access to just certain GraphQL operations, you can do this for the root Query, Mutation, and Subscription fields.

{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "appsync:GraphQL" ], "Resource": [ "arn:aws:appsync:us-west-2:123456789012:apis/YourGraphQLApiId/types/Query/fields/<Field-1>", "arn:aws:appsync:us-west-2:123456789012:apis/YourGraphQLApiId/types/Query/fields/<Field-2>", "arn:aws:appsync:us-west-2:123456789012:apis/YourGraphQLApiId/types/Mutation/fields/<Field-1>", "arn:aws:appsync:us-west-2:123456789012:apis/YourGraphQLApiId/types/Subscription/fields/<Field-1>", ] ] } }

For example, suppose you have the following schema and you want to restrict access to getting all posts:

schema { query: Query mutation: Mutation } type Query { posts:[Post!]! } type Mutation { addPost(id:ID!, title:String!):Post! }

The corresponding IAM policy for a role (that you could attach to an Amazon Cognito identity pool, for example) would look like the following:

{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "appsync:GraphQL" ], "Resource": [ "arn:aws:appsync:us-west-2:123456789012:apis/YourGraphQLApiId/types/Query/fields/posts" ] } ] } ] }


This authorization type enforces OIDC tokens provided by Amazon Cognito User Pools. Your application can leverage the users and groups in your user pools and associate these with GraphQL fields for controlling access.

When using Amazon Cognito User Pools, you can create groups that users belong to. This information is encoded in a JWT token that your application sends to AWS AppSync in an authorization header when sending GraphQL operations. You can use GraphQL directives on the schema to control which groups can invoke which resolvers on a field, thereby giving more controlled access to your customers.

For example, suppose you have the following GraphQL schema:

schema { query: Query mutation: Mutation } type Query { posts:[Post!]! } type Mutation { addPost(id:ID!, title:String!):Post! } ...

If you have two groups in Amazon Cognito User Pools - bloggers and readers - and you want to restrict the readers so that they cannot add new entries, then your schema should look like this:

schema { query: Query mutation: Mutation }
type Query { posts:[Post!]! @aws_auth(cognito_groups: ["Bloggers", "Readers"]) } type Mutation { addPost(id:ID!, title:String!):Post! @aws_auth(cognito_groups: ["Bloggers"]) } ...

Note that you can omit the @aws_auth directive if you want to default to a specific grant-or-deny strategy on access. You can specify the grant-or-deny strategy in the user pool configuration when you create your GraphQL API via the console or via the following CLI command:

$ aws appsync --region us-west-2 create-graphql-api --authentication-type AMAZON_COGNITO_USER_POOLS --name userpoolstest --user-pool-config '{ "userPoolId":"test", "defaultEffect":"ALLOW", "awsRegion":"us-west-2"}'

Fine-Grained Access Control

The preceding information demonstrates how to restrict or grant access to certain GraphQL fields. If you want to set access controls on the data itself based on certain conditions - such as who the user is that is making a call and whether they own the data - you can do use mapping templates in your resolvers. You can also perform more complex business logic, which we describe in Filtering Information.

This section shows how to set access controls on your data using a DynamoDB resolver mapping template.

Before proceeding any further, if you're not familiar with mapping templates in AWS AppSync, you may want to review the Resolver Mapping Template Reference and the Resolver Mapping Template Reference for DynamoDB.

In the following example using DynamoDB, suppose you're using the preceding blog post schema, and only users that created a post are allowed to edit it. The evaluation process would be for the user to gain credentials in their application, using Amazon Cognito User Pools for example, and then pass these credentials as part of a GraphQL operation. The mapping template will then substitute a value from the credentials (like the username)in a conditional statement which will then be compared to a value in your database.

To add this functionality, add a GraphQL field of editPost as follows:

schema { query: Query mutation: Mutation } type Query { posts:[Post!]! } type Mutation { editPost(id:ID!, title:String, content:String):Post addPost(id:ID!, title:String!):Post! } ...

The resolver mapping template for editPost (shown in an example at the end of this section) needs to perform a logical check against your data store to allow only the user that created a post to edit it. Since this is an edit operation, it corresponds to an UpdateItem in DynamoDB. You can perform a conditional check before performing this action, using context passed through for user identity validation. This is stored in an Identity object that has the following values:

{ "accountId" : "12321434323", "cognitoIdentityPoolId" : "", "cognitoIdentityId" : "", "sourceIP" : "", "caller" : "ThisistheprincipalARN", "username" : "username", "userArn" : "Sameasabove" }

To use this object in a DynamoDBUpdateItem call, you need to store the user identity information in the table for comparison. First, your addPost mutation needs to store the creator. Second, your editPost mutation needs to perform the conditional check before updating.

Here is an example of the request mapping template for addPost that stores the user identity as an Author column:

{ "version" : "2017-02-28", "operation" : "PutItem", "key" : { "postId" : { "S" : "${}" } }, "attributeValues" : { "Author" : {"S" : "${context.identity.username}"} #foreach( $entry in $context.arguments.entrySet() ) #if( $entry.key != "id" ) ,"${entry.key}" : { "S" : "${entry.value}" } #end #end }, "condition" : { "expression" : "attribute_not_exists(postId)" } }

Note that the Author attribute is populated from the Identity object, which came from the application.

Finally, here is an example of the request mapping template for editPost, which only updates the content of the blog post if the request comes from the user that created the post:

{ "version" : "2017-02-28", "operation" : "UpdateItem", "key" : { "postId" : { "S" : "${}" } }, "attributeValues" : { "Author" : {"S" : "${context.identity.username}"} #foreach( $entry in $context.arguments.entrySet() ) ,"${entry.key}" : { "S" : "${entry.value}" } #end }, "condition" : { "expression" : "Author = :authorName", "expressionValues" : { ":authorName" : { "S" : "${context.identity.username}" } } } }

Filtering Information

There may be cases where you cannot control the response from your data source, but you don't want to send unnecessary information to clients on a successful write or read to the data source. In these cases, you can filter information by using a response mapping template.

For example, suppose you don't have an appropriate index on your blog post DynamoDB table (such as an index on Author). You could run a GetItem query with the following mapping template:

{ "version" : "2017-02-28", "operation" : "GetItem", "key" : { "postId" : { "S" : "${}" } } }

This returns all the values responses, even if the caller isn't the author who created the post. To prevent this from happening, you can perform the access check on the response mapping template in this case as follows:

{ #if($context.result["Author"] == "$context.identity.username") $utils.toJson($context.result); #end }

If the caller doesn't match this check, only a null response is returned.