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