There are several ways to authorize your application to interact with the AWS AppSync GraphQL API. But what if your application has no authentication? This post shows you a best practice for communicating with AWS AppSync for public web sites.
TL;DR - Use Cognito Identity Pools with IAM Roles.
I found many comments on GitHub and Stack Overflow that deal with this issue. But no solution felt good until I found the GitHub repo from dabit3.
AWS AppSync supports AWS_IAM. With the Cognito Identity Pool you can associate the IAM policy.
Code, Code and Code
In the following two steps I explain which changes are necessary.
Step 1: Configure AWS AppSync
The first step is to specify the authentication type in aws-exports.js. Set the authenticationType to 'AWS_IAM'.
File: aws-exports.js
export default {
  graphqlEndpoint: process.env.AWS_APPSYNC_GRAPHQL_ENDPOINT,
  region: 'eu-central-1',
  authenticationType: 'AWS_IAM'
}
Update the AppSync configuration by setting credentials to () => Auth.currentCredentials().
File: index.js
import React from 'react'
import ReactDOM from 'react-dom'
import Auth from '@aws-amplify/auth'
import AWSAppSyncClient from 'aws-appsync'
import { ApolloProvider } from '@apollo/client'
import { Rehydrated } from 'aws-appsync-react'
import App from './App'
import AppSyncConfig from './aws-exports'
const appSyncConfig = {
  url: AppSyncConfig.graphqlEndpoint,
  region: AppSyncConfig.region,
  auth: {
    type: AppSyncConfig.authenticationType,
    credentials: () => Auth.currentCredentials()
  },
  disableOffline: true // https://github.com/awslabs/aws-mobile-appsync-sdk-js/issues/102
}
const appSyncOptions = {
  defaultOptions: {
    watchQuery: {
      fetchPolicy: 'cache-and-network'
    }
  }
}
const client = new AWSAppSyncClient(appSyncConfig, appSyncOptions)
ReactDOM.render(
  <ApolloProvider client={client}>
    <Rehydrated>
      <App />
    </Rehydrated>
  </ApolloProvider>,
  document.getElementById('app')
)
Not so important
The rest of the following code is only for the completeness of the demo application.
File: App.js
import React from 'react'
import { Query } from '@apollo/client/react/components'
import Auth from '@aws-amplify/auth'
import { LIST_EVENTS } from './graphql/queries.gql'
Auth.configure({
  region: process.env.AWS_COGNITO_REGION,
  identityPoolId: process.env.AWS_COGNITO_IDENTITIY_POOL_ID
})
export default () => (
  <>
    <h1>Events</h1>
    <Query query={LIST_EVENTS}>
      {({ loading, error, data }) => (
        data.listEvents.items.map(event => (
          <div key={`event_${event.id}`}>
            <h2>{event.name}</h2>
            <p>{event.date}</p>
          </div>
        ))
      )}
    </Query>
  </>
)
File: queries.gql
query LIST_EVENTS {
  listEvents {
    items {
      id
      name
      date
    }
  }
}
File: schema.graphql
type Event {
  id: ID!
  name: String!
  date: AWSDateTime!
}
type ModelEventConnection {
  items: [Event]
  nextToken: String
}
type Query {
  listEvents(
    limit: Int,
    nextToken: String
  ): ModelEventConnection
}
schema {
  query: Query
}
Step 2: Setup Cognito Identity Pool
In my example I use the Serverless Framework.
First we define the Cognito Identity Pool and set AllowUnauthenticatedIdentities: true. This enable access for unauthenticated identities.
In the second part I link the role for the Identity Pool. BTW: You can also set an role for authenticated users via authenticated if your application supports authenticated and unauthenticated users.
At the last step, I create the IAM role and the related policy. Add your resources (Query, Mutation, Subscription) to the policy.
File: serverless.yml
## Cognito Identity Pool
CognitoIdentityPool:
  Type: AWS::Cognito::IdentityPool
  Properties:
    IdentityPoolName: ${self:service}-${self:provider.stage}-${self:provider.region}-IdentityPool
    AllowUnauthenticatedIdentities: true
## IAM roles
CognitoIdentityPoolRoles:
  Type: AWS::Cognito::IdentityPoolRoleAttachment
  Properties:
    IdentityPoolId:
      Ref: CognitoIdentityPool
    Roles:
      unauthenticated:
        !GetAtt CognitoUnAuthRole.Arn
## IAM role used for unauthenticated users
CognitoUnAuthRole:
  Type: AWS::IAM::Role
  Properties:
    Path: /
    AssumeRolePolicyDocument:
      Version: '2012-10-17'
      Statement:
        - Effect: 'Allow'
          Principal:
            Federated: 'cognito-identity.amazonaws.com'
          Action:
            - 'sts:AssumeRoleWithWebIdentity'
          Condition:
            StringEquals:
              'cognito-identity.amazonaws.com:aud':
                Ref: CognitoIdentityPool
            'ForAnyValue:StringLike':
              'cognito-identity.amazonaws.com:amr': unauthenticated
    Policies:
      - PolicyName: ${self:service}-${self:provider.stage}-${self:provider.region}-AppSyncCognitoPolicy
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: 'Allow'
              Action:
                - 'mobileanalytics:PutEvents'
                - 'cognito-sync:*'
                - 'cognito-identity:*'
              Resource: '*'
            - Effect: Allow
              Action:
                - appsync:GraphQL
              Resource:
                !Join [ '', [ !GetAtt GraphQlApi.Arn, '/types/Query/fields/listEvents' ] ]
Ready
🏁 Now you have access to AWS AppSync and the listEvents query can be executed without authentication.
 
 
              
 
    
Top comments (2)
I am yet to try your solution but the AWS documentation for this topic is a complete joke. Thank you for posting this and hopefully tomorrow I have some good luck with it as today was slow progress!
I am currently trying this and my first question is going to be where in the serverless.yml do we insert those values. I think under resources:Resources: but I am not sure yet