AWS AppSync without Authentication

danielbayerlein

Daniel Bayerlein

Posted on March 10, 2020

AWS AppSync without Authentication

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'
}
Enter fullscreen mode Exit fullscreen mode

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')
)

Enter fullscreen mode Exit fullscreen mode

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>
  </>
)
Enter fullscreen mode Exit fullscreen mode

File: queries.gql

query LIST_EVENTS {
  listEvents {
    items {
      id
      name
      date
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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' ] ]
Enter fullscreen mode Exit fullscreen mode

Ready

🏁 Now you have access to AWS AppSync and the listEvents query can be executed without authentication.

💖 💪 🙅 🚩
danielbayerlein
Daniel Bayerlein

Posted on March 10, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related