Jerry Emilo

Appsync IAM policy scoped for schema introspection and a single query

✏️ Summary

Every API has it’s own requirements. Some APIs are completely public, others might have strict access policies for certain resources. In the world of GraphQL there is generally one endpoint. However, the queries and mutations available available within that endpoint can be scoped for example at the request level or maybe programmatically within the query or mutation for let’s say something like the a user group.

When using AWS Appsync there quite a few ways to do this. It can be done using an API key, IAM user with a policy, or even using services like Cognito.

In this post won’t be going into a full example of how to setup a GraphQL using AWS Appsync. If you’re interested in learning more using a fully baked example check out GraphQL with AWS AppSync and Go Lambdas.

🔎 Use Case

In our example let’s pretend a user has another API or program that wants to use our API. However, we only want them to be able to access certain queries, mutations, and the GraphQL schema introspection. Then we will need an IAM user with a policy to provide programmatic access to our resources.

So first we need to create our GraphQL AWS Appsync. Feel free to use the AWS Console in a web browser. The examples here will use Cloudformation.

💻 Example

Create API and schema

Let’s assume we have a schema.graphql in the root of project.

AppSyncAPI:
  Type: AWS::AppSync::GraphQLApi
  Properties:
    Name: "your_cool_graphql_api_name"
    AuthenticationType: AWS_IAM

AppSyncSchema:
  Type: AWS::AppSync::GraphQLSchema
  Properties:
    ApiId: !GetAtt [AppSyncAPI, ApiId]
    DefinitionS3Location: ./schema.graphql

Schema Definition

In our previous example GraphQL with AWS AppSync and Go Lambdas, API keys were used for authentication. This is great for simple security, however when trying to be more granular using an IAM user with a policy makes things a bit more flexible.

"""
A coffee type for our coffee resource type
"""
type Coffee @aws_iam {
  id: ID!
  name: String!
  origin: String!
  roast: String!
}

"""
Create coffee input for our coffee resource type
"""
input CreateCoffee @aws_iam {
  name: String!
  origin: String!
  roast: String!
}

"""
Update coffee input for our coffee resource type
"""
input UpdateCoffee @aws_iam {
  id: ID!
  name: String
  origin: String
  roast: String
}

"""
Delete coffee input for our coffee resource type
"""
input DeleteCoffee @aws_iam {
  id: ID
  name: String
  origin: String
  roast: String
}

"""
Input for getting a random coffee
"""
input RandomCoffees @aws_iam {
  quantity: Int
}

"""
Connection type for our coffee resource to help support pagination
"""
type CoffeeConnection @aws_iam {
  items: [Coffee]
  nextToken: String
}

"""
A type for the result of requesting a random coffee
"""
type RandomCoffeeResult @aws_iam {
  coffees: [Coffee]
}

"""
The queries our service supports
"""
type Query @aws_iam {
  """
  Get a coffee resource by ID
  """
  getCoffee(id: ID!): Coffee
  """
  Get all coffee resources
  """
  allCoffees(nextToken: String): CoffeeConnection
  """
  Get a random set of coffee resources
  """
  getRandomCoffees(input: RandomCoffees): RandomCoffeeResult
}

"""
The mutations our service supports
"""
type Mutation @aws_iam {
  """
  Create a coffee resource
  """
  createCoffee(input: CreateCoffee!): Coffee
  """
  Update a coffee resource
  """
  updateCoffee(input: UpdateCoffee!): Coffee
  """
  Delete a coffee resource
  """
  deleteCoffee(input: DeleteCoffee!): Coffee
}

"""
Our schema
"""
schema {
  query: Query
  mutation: Mutation
}

IAM User and Policy

Then let’s make an IAM user with a policy that is scoped to just getting a single coffee by ID and permission to get the schema introspection. Access to the schema introspection is a big deal. It allows users to be able to get the schema documentation. One of the big benefits to using GraphQL is that it’s self documenting.

CoolIAMUser:
  Type: AWS::IAM::User
  Properties:
    UserName: "your_cool_graphql_iam_user"
    ManagedPolicyArns:
      - !Ref CoolIAMUserPolicy

CoolIAMUserPolicy:
  Type: AWS::IAM::ManagedPolicy
  Properties:
    ManagedPolicyName: your_cool_graphql_iam_user_policy
    PolicyDocument:
      Version: "2012-10-17"
      Statement:
        - Effect: Allow
          Action:
            - appsync:GraphQL
          Resource:
            - !Sub "${AppSyncAPI.Arn}/types/Query/fields/__schema"
            - !Sub "${AppSyncAPI.Arn}/types/Query/fields/getCoffee"

✌️ Review

This was just a brief summary of how to use IAM to add security to an AWS Appsync GraphQL API endpoint. The inspiration for this post was actually being frustrated with how to add security for the introspection query. Even though the GraphQL documentation does exist for specifying __schema, it’s not listed as an example anywhere that I could find on Appsync documentation. So after some serious struggle, I threw up a proxy to see what the Insomnia Client was querying for. Then was able to reverse engineer it a bit. This was definitely the result of just being a GraphQL noob, but I figured it was worth sharing so people could not lose their precious time. Also make sure to check out GraphQL with AWS AppSync and Go Lambdas if you want a full example of how all of this works together. Thanks for reading 🍻

📨 Questions, Issues, and Suggestions?

Please feel free to send over any questions, issues, or suggestions.