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