Front-End Web & Mobile

GraphQL API Security with AWS AppSync and Amplify

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

September 14, 2021: Amazon Elasticsearch Service has been renamed to Amazon OpenSearch Service. See details.

AWS AppSync is a fully managed service which allows to deploy and interact with serverless scalable GraphQL backends on AWS. AppSync uses security best practices that AWS has developed operating large systems at scale in the cloud, with built-in DDoS protection in all its GraphQL API endpoints leveraging the infrastructure, technologies, and techniques that AWS uses for many other services. As a managed GraphQL service, AppSync provides reliable enterprise security features to manage access to GraphQL endpoints. In this article, we go over some of AppSync’s security features and describe when and how you might want to use them in your own applications. Amplify is a platform and framework used to build secure and scalable applications in the cloud with enhanced development velocity. We use the Amplify GraphQL Transform @auth directive to demonstrate how to easily create GraphQL schema definitions and authorization rules that support these scenarios in AppSync APIs as part of an Amplify project.

With AWS AppSync, you create GraphQL APIs that your applications interact with over the internet. While the API endpoints are publicly reachable, they never allow unauthorized access. A method of authorization — a token in the request header or signing the request itself with AWS credentials — is always required to access your AppSync API. AppSync provides four different authorization modes:

  • API Keys (API_KEY)
  • Amazon Cognito User Pools (AMAZON_COGNITO_USER_POOLS)
  • OpenID Connect (OPENID_CONNECT)
  • AWS Identity and Access Management (AWS_IAM)

AppSync APIs must have a default authorization mode defined globally. It is possible to add additional authorization modes in the same API as well as mix and match these modes, linking specific authorization providers to types, fields, or operations in the GraphQL schema. In order to do so, you can use AppSync-specific directives to configure authorization and security at your data definition level directly in the schema.

For instance, given an AppSync API using API Keys as the default authorization mode and Cognito User Pools as an additional authorization mode, the following type definition in an API GraphQL schema grants access to the Post type for both authorization modes:

type Post @aws_api_key @aws_cognito_user_pools {
   id: ID!
   description: String
}

This other example grants access only to the Cognito User Pools authorization mode.

type Post @aws_cognito_user_pools {
   id: ID!
   description: String
}

When an authorization directive is added to a type, all fields of the type are made available to that mode by default. We can further restrict access to certain fields by adding the directive on the fields we wish to restrict.

type Post @aws_api_key @aws_cognito_user_pools {
   id: ID!
   description: String @aws_cognito_user_pools
}

This definition allows both modes to access the type and the id field. However, only the Cognito User Pools authorization mode can access the description field. Note that we must grant access to the type and the field for the field-level authorization to take effect. Secondary authorization modes are denied by default throughout the schema and you have to configure them explicitly in a cascading manner for related connections and operations.

When using the GraphQL Transform to define and deploy your AppSync API in an Amplify project, you can take advantage of the @auth directive to write authorization rules in the GraphQL schema for each supported authorization mode. When using the userPools and oidc providers in Amplify, you can specify additional rule settings to automatically generate fine-grained access control business logic. Here’s how AppSync authorization directives are related to the @auth directive rules in Amplify:

AppSync directives Amplify @auth equivalent rule
@aws_api_key { allow: public, provider: apiKey }
@aws_iam { allow: public/private, provider: iam}
@aws_oidc { allow: private/owner, provider: oidc}
@aws_cognito_user_pools
@aws_auth
{ allow: private/owner/groups, provider: userPools }

The AppSync directives on the left should be used if working directly with AppSync, for instance when editing the GraphQL API schema in the AWS Console or when using CloudFormation to define the schema in a template. The rules on the right should be used when defining GraphQL API authorization with the @auth directive in Amplify projects (i.e.: using amplify add api with the Amplify CLI).

Next we take a closer look at the different authorization modes with recommended use cases and configuration examples. After that, we discuss securely accessing VPC resources from your GraphQL APIs as well as provide a quick word on security and compliance standards with AppSync.

API Key

This method allows to define a static API key used to authorize a request with the HTTP header x-api-key. You create the key with an expiration date and AppSync accepts requests that use the API key while the key exists and has not expired. API keys are configurable for up to 365 days, and you can extend an existing expiration date for up to another 365 days from that day. A key cannot be extended after it expires.

When to use

The API key is a hardcoded value in your application. API Keys are recommended for development purposes or use cases where it is safe to provide public access to an API without specific authentication requirements (i.e. guest users). It is recommended to use API keys when you are getting started with the API development, want to iterate quickly, and don’t want to worry about more complicated authorization methods. Applications expected to be long-lived and widely distributed should not use API keys unless you have use cases where all or part of the application will always support guest access.

Configuration example

If you are using the API key as an additional authorization mode in AppSync, you can use the directive @aws_api_key in your schema to specify the field is API_KEY authorized. Note that the configuration below only grants access to API key authorized requests.

type Query {
   getPosts: [Post] @aws_api_key
}

You can achieve the same results using the Amplify @auth transform on a @model backed type.

type Post
  @model
  @auth(rules: [{ allow: public, operations: [read] }]){
  id: ID!
  description: String
}

This grants public access to read posts when using the API_KEY access method. When granting public access, Amplify uses the API_KEY access method by default. For this use case, you can but do not need to specify the provider.

Amazon Cognito User Pools

This authorization method leverages Amazon Cognito User Pools. After signing in with Cognito User Pools and receiving JSON web tokens (JWTs), your application uses the tokens to authorize requests with your AppSync API. Information about the user, its groups, and specific attributes or claims can be used for granular access control at the AppSync layer. For instance, you can restrict (read and/or write) access to specific types based on the user’s identity or group membership.

When to use

Amazon Cognito User Pools provide a fully managed user directory. Cognito User Pools also allows users to sign-in to your application using their social profile (e.g.: Google, Facebook, Amazon, or Apple). It’s also possible to federate a User Pool with existing SAML identity providers and OpenID Connect (OIDC) Identity providers.

Cognito also allows for a secure way to exchange JWT tokens from User Pools with temporary AWS credentials using Cognito Identity Pools. It makes Cognito User Pools a good choice when your application must interact with an AppSync endpoint using JWT tokens as well as other AWS services using AWS credentials.

Configuration example

When Cognito User Pools is the only authorization mode defined in an AppSync API, you can use the @aws_auth AppSync directive to limit access to certain cognito groups. For example, the schema below limits access to users that are part of the Bloggers and Readers group.

type Query {
  getPosts:[Post!]! 
  @aws_auth(cognito_groups: ["Bloggers", "Readers"])
}

If using additional authorization modes , you should use the @aws_cognito_user_pools AppSync directive to specify that the field should be AMAZON_COGNITO_USER_POOLS authorized.

type Query {
  getPosts:[Post!]!
  @aws_api_key @aws_cognito_user_pools(cognito_groups: ["Bloggers", "Readers"])
}

When working with @model or @searchable types in an Amplify project, you can use the GraphQL Transform @auth directive to automatically create specific authorization business logic in AppSync that allows access to Amazon DynamoDB tables or Amazon OpenSearch Service (successor to Amazon Elasticsearch Service) clusters based on the user’s identity or group membership. The example below allows the owner (identified by the username of the Cognito User Pools user) full access to edit the posts she owns and restricts members of the “Bloggers” and “Readers” group to only read the posts. When the user creates a post, the authorization logic in the AppSync resolvers attaches the user name to the record. The user name and group membership are then automatically validated for access on the next operations.

type Post
  @model
  @auth(
    rules: [
      { allow: owner }
      { allow: groups, groups: ["Bloggers", "Readers"], operations: [read] }
    ]
  ) {
  id: ID!
  description: String
}

In addition to Cognito User Pools built-in static groups, you can also authorize group access dynamically based on the group information stored with each record. When operating on a record, the group membership of users is checked against the groups defined in the group field stored in DynamoDB.

type Post
  @model
  @auth(
    rules: [
      { allow: owner }
      { allow: groups, groupsField: "group", operations: [read] }
    ]
  ) {
  id: ID!
  description: String
  group: [String] # or String for a single group
}

OpenID Connect

The OpenID Connect (OIDC) authorization method is comparable to the Amazon Cognito User Pools method. In this scenario, you configure AppSync with a fully compliant OIDC identity provider (IdP) such as Auth0 as an authorization provider. Upon receiving a request, AppSync validates the access token using the OIDC IdP’s JSON Web Key (JWK) set. If the validation is successful, AppSync authorizes the request.

When to use

You can use the OIDC authorization method if you have an existing user base and are not planning on accessing other AWS services from your application using AWS credentials. If your applications need access to other services using AWS credentials for authorization, you may want to use Cognito User Pools and federate the existing OIDC IdP to the user pool.

Configuration example

When using additional authorization modes, you can leverage the @aws_oidc AppSync directive to specify that the field is OPENID_CONNECT authorized.

type Query {
  getPosts:[Post!]! @aws_oidc  
}

In an Amplify project you can use the @auth transform with OpenID Connect, in the same way as with Cognito User Pools, by specifying oidc as the provider in the rule definition. The @auth directive supports custom claims for both Cognito User Pools and OIDC. With an OIDC provider, Amplify makes no assumption of which claims hold the user identity information and group membership. You specify the claim to be used to check identity and group membership with identityClaim and groupClaim respectively.

type Post
  @model
  @auth(
    rules: [
      { allow: owner, provider: oidc, identityClaim: "custom:id" }
      {
        allow: groups
        groups: ["Bloggers", "Readers"]
        operations: [read]
        provider: oidc
        groupClaim: "custom:groups"
      }
    ]
  ) {
  id: ID!
  description: String
}

AWS Identity and Access Management (IAM)

With the IAM authorization mode, requests are signed using the AWS Signature Version 4 Signing Process and AppSync grants access based on the associated permissions. When using this mode, you create an IAM entity (user or role) and define permissions in the policies attached to the entity. For example, the following policy allows to retrieve posts (and all its fields) using the getPost and listPosts queries for a given AppSync API:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["appsync:GraphQL"],
      "Resource": [
        "arn:aws:appsync:us-east-1:123456789012:apis/GraphQLApiId/types/Query/fields/getPost",
        "arn:aws:appsync:us-east-1:123456789012:apis/GraphQLApiId/types/Query/fields/listsPosts",
        "arn:aws:appsync:us-east-1:123456789012:apis/GraphQLApiId/types/Post/*"
      ]
    }
  ]
}

Refer to the GraphQL action in the list of actions defined by AWS AppSync for IAM policies for more information.

When to use

The IAM authorization mode is a great fit when used with backend systems (e.g.: Amazon EC2 instances or AWS Lambda) that can be securely configured with AWS credentials. Amazon EC2 instances can be configured with instance profiles and AWS Lambda functions are configured with an execution role. See the Amplify documentation for an example of how to get started on calling GraphQL APIs in AppSync from Lambda.

This authorization mode could also be used when your application needs to provide guest (public) access to your AppSync API. Guest access is accomplished with IAM using Amazon Cognito Identity Pools unauthenticated identities. Use unauthenticated roles for guest access if clients also need to access AWS services like S3 before the user logs in to the application. However, ensure that unauthenticated role policies are scoped to give minimal access to AWS resources following the principle of least privilege. Temporary credentials for unauthenticated and authenticated users are managed automatically with the Amplify Authentication module.

Configuration example

When using additional authorization modes, you can use the @aws_iam AppSync directive to specify that the field is AWS_IAM authorized.

type Post @aws_iam @aws_cognito_user_pools {
  id: ID!
  description: String
  rating: Int @aws_iam # restrict this field to AWS_IAM
}

The @auth Amplify transform makes it easy to configure an authorization rule that allows applications with IAM credentials to access the API. The configuration below allows users or services with proper AWS credentials and the right permissions to read and update posts. You can use this configuration to allow an event driven Lambda function to periodically update the ratings of the posts in your application.

type Post
  @model
  @auth(
    rules: [
      { allow: owner }
      { allow: private, provider: iam, operations: [read, update] }
    ]
  ) {
  id: ID!
  description: String
  rating: Int @auth(rules: [{ allow: private, provider: iam }])
}

Similarly, you can define public access with IAM as an authorization provider. If you have also defined an Auth category with Amplify (i.e.: using amplify add auth with the Amplify CLI), it creates a new policy granting read access to posts and automatically attaches it to the unauthorized identity of a Cognito Identity Pool. In your application, you can then request temporary credentials to sign your AppSync requests and read posts.

type Post
  @model
  @auth(
    rules: [{ allow: public, provider: iam, operations: [read] }]
  ) {
  id: ID!
  description: String
}

Here we are also providing public access, but this time we specify the provider iam.

 

Securing access to VPC resources

AppSync APIs are public however, as discussed, the endpoints have built-in DDoS protection and you can configure granular authorization logic from multiple authorization providers to access the data AppSync exposes to client applications. It’s not possible to have an AppSync API without some sort of authorization mechanism associated with it.

In order to connect AppSync GraphQL APIs to VPC resources such as databases, containers, or other internal APIs behind application or network load balancers, you can leverage AWS Lambda. With recent network improvements and the new provisioned concurrency capabilities, Lambda functions accessing VPC resources can be highly performant and provide a great way to add business logic as well as securely access VPC resources with properly defined and scoped AWS IAM credentials.

For more information on AppSync APIs securely accessing VPC resources such as Amazon Neptune databases, Amazon ElastiCache clusters or AWS Fargate containers please refer to the articles Integrating alternative data sources with AWS AppSync and Simplify access to multiple microservices with AWS AppSync and AWS Amplify.

 

Compliance requirements

Other than all the great security features we mentioned above, AppSync is also fully compliant with different industry standards such as ISO, PCI, SOC, IRAP, HIPAA, MTCS, C5, ENS High, OSPAR, HITRUST CSF, and others. For more information, please refer to AWS Services in Scope.

If you have a business requirement that demands compliance with one or more of the security standards above, AppSync helps getting your application in scope and compliant.

 

Next steps

Getting security right and implementing proper authorization rules, while essential, is not trivial. AWS AppSync implements multiple authorization modes to cater to different access patterns and the AWS Amplify @auth directive allows you to confidently put them to use in your API and applications. Here are a few things to keep in mind as you build out secure APIs with AppSync and Amplify:

  • Turn on additional authorization modes to enable different types of access to your API.
  • Leverage fine-grained access control with Amazon Cognito User Pools or OIDC.
  • Grant guest public access to your APIs by leveraging API keys or unauthenticated identities with Amazon Cognito Identity Pools.
  • Use scoped-down IAM permissions to grant backend processes access to your APIs.

With built-in scalability, reliability, DDoS protection, multiple interconnected authorization modes, serverless functions with improved network capabilities and pre-provisioned capacity, reliable IAM access control, and built-in VPC security features, you can build a powerful and performant end to end architecture to securely access VPC resources via GraphQL with AppSync.

To learn more about AppSync security, please check our documentation. To get started with building applications and APIs with Amplify, refer to the Amplify Framework documentation. It provides a CLI, libraries and the GraphQL Transform that you can use to get started with developing critical security functionality in your application.

What else would you like to see in AWS AppSync security? Let us know if you have any ideas, if so feel free to create a feature request in our GitHub repository. Our team constantly monitors the repository and we’re always interested on developer feedback. Go build securely with AppSync and Amplify!