The goal is to create a backend that allows us to:
- Signup and login
- Create and manage posts through a GraphQL API
- Enable everyone (also unauthenticated users) to READ all posts
- Prevent users from editing posts by others
We run the following commands to setup our Amplify project:
amplify init
? Initialize amplify with your prefered settings
amplify add auth
? Do you want to use the default authentication and security configuration
Default configuration
? How do you want users to be able to sign in?
Username
? Do you want to configure advanced settings?
No, I am done.
# I went with all the defaults. Configure however you'd like!
amplify add api
? Please select from one of the below mentioned services:
GraphQL
? Provide API name:
amplifyPosts
? Choose the default authorization type for the API Amazon Cognito User Pool
Use a Cognito user pool configured as a part of this project.
# Using cognito user pool for signing up and logging in users
? Do you want to configure advanced settings for the GraphQL API
Yes, I want to make some additional changes.
? Configure additional auth types?
Yes
? Choose the additional authorization types you want to configure for the API
API key
# A public API Key is needed to read ALL the posts
API key configuration
? Enter a description for the API key:
amplifyPosts
? After how many days from now the API key should expire (1-365):
7
? Configure conflict detection?
No
? Do you have an annotated GraphQL schema?
No
? Do you want a guided schema creation?
Yes
? What best describes your project:
Single object with fields (e.g., βTodoβ with ID, name, description)
? Do you want to edit the schema now?
Yes
# This is where you copy and paste the schema below
β GraphQL schema compiled successfully.
amplify add codegen
? Choose the code generation language target
javascript
? Enter the file name pattern of graphql queries, mutations and subscriptions
src/graphql/**/*.js
? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions
Yes
? Enter maximum statement depth [increase from default if your schema is deeply nested]
2
β Generated GraphQL operations successfully and saved at src/graphql
The schema:
type Post
@model
@auth(
rules: [
# The owner is allowed to do everything
{ allow: owner }
# The public is only allowed to read posts
{ allow: public, operations: [read] }
]
) {
id: ID!
title: String!
content: String
owner: String
}
At this point we can either run amplify push
to deploy our backend, or amplify mock
to mock our backend locally (this still required you to deploy the auth module first, but the mock
command will guide you through that!)
At this point, add some users and posts to the backend by writing some mutations in the Amplify Console (or in the GraphiQL client at http://localhost:20002 if you used amplify mock
).
After you push
or mock
your backend, Amplify will generate some files for you in your specified src directory:
-
aws-exports.js
- A configuration file for our frontend to communicate with our backend -
src/graphql/{mutations,queries,subscriptions}.js
- A starting point for GraphQL queries so we don't have to write them ourselves (you can write more optimized ones tho if you want!)
Now's the time to consume the backend in our frontend.
Install aws-amplify
and whatever aws package that fits your framework (We use react
and aws-amplify-react
):
yarn add aws-amplify aws-amplify-react
Somewhere high up in our component tree, we configure Amplify
:
import Amplify from 'aws-amplify'
import awsconfig from '../aws-exports'
Amplify.configure(awsconfig)
To query for the posts created by the authenticated user:
import React, { useState, useEffect } from 'react'
import { API, graphqlOperation } from 'aws-amplify'
import { withAuthenticator } from 'aws-amplify-react'
import { listPosts } from '../graphql/queries'
const MyPostsScreen = () => {
const [myPosts, setMyPosts] = useState([])
useEffect(() => {
async function getPosts() {
const { data } = await API.graphql(graphqlOperation(listPosts))
setMyPosts(data.listPosts.items)
}
getPosts()
}, [])
return (
<div>
My Posts:
<ul>
{myPosts.map(post => (
<Post key={post.id} post={post} />
))}
</ul>
</div>
)
}
// withAuthenticator wraps our components in a signup/login, which makes sure we are logged in when accessing this component!
export const AuthenticatedMyPostsScreen = withAuthenticator(MyPostsScreen)
Now, to query for ALL posts, all we need to do is switch the authMode to API_KEY
:
import React, { useState, useEffect } from 'react'
import { API } from 'aws-amplify'
import { listPosts } from '../graphql/queries'
export const AllPostsScreen = () => {
const [allPosts, setAllPosts] = useState([])
useEffect(() => {
async function getPosts() {
// Switch authMode to API_KEY
const { data } = await API.graphql({
query: listPosts,
authMode: 'API_KEY',
})
setPosts(data.listPosts.items)
}
getPosts()
}, [])
return (
<div>
All Posts:
<ul>
{allPosts.map(post => (
<Post key={post.id} post={post} />
))}
</ul>
</div>
)
}
That's it! Because of the way our GraphQL schema uses the @auth
directive, it is still impossible to edit posts that you don't own. Trying to run a mutation using authMode: 'API_KEY'
will result in an Unauthorized
error.
It took me a while to figure out you need to switch the authMode
. If there's a better or easier way to do this, I'd love to hear about it!
Cheers.
Top comments (8)
This article is well written, the Amplify docs still have a long way to go... This was not an easy thing to figure out just out of the docs.
The main things that tripped me up were that the sequence in which to add Auth first with Cognito user pools and THEN adding the API with the API key made a big difference.
The second thing that tripped me up was in the amplify CLI menus
[space to select] and [enter] to confirm choices... When you've gone through the flow 10 times you habitually scroll to an option and hit enter, and in the back of your mind that should have been selected...it was not.
Noob mistake, but maybe others can learn from it.
@robert , thanks for this article!
Nice! The multiple authorizer pattern for one AppSync GraphQL endpoint is an eventual necessity, IMHO. So now I'm wondering out loud for discussion, which authorizer is the best default one? My project starting point was different. I started with API_KEY. The API_KEY AppSync authorizer is perfect for calling the GraphQL endpoint with superuser powers. It's simple. @auth is perfect for entities that are specific to a web app user. I added a AMAZON_COGNITO_USER_POOLS authorizer, second. That's how I ended up with multiple authorizers, too. I started with one default AppSync authorizer: API_KEY. I added the second AppSync endpoint authorizer with
amplify update api
. Which is kinda same place though the default authorizer for an endpoint is simply different.What you could also do is initialize 2 API clients, one with
API_KEY
as the default and one withAMAZON_COGNITO_USER_POOLS
as the default, then use the public one in the public part of your app, and the private one in the private part of your app.I'm not sure if this is possible using Amplify, but you could simply write your own wrapper!
Slightly less code, to not have to set 'authMode' for each API.graphql() invocation! This is very interesting. A single AppSync endpoint has a single default authorizer. A single Amplify GraphQL client instance may have a single default authorization mode. It's up to the developer to evolve this god for saken security model!
Thanks for putting this together! I've been trying to figure out how to make a graphql API have public models and enforce user pool permissions on others. I've gone through what you've done, but i think the Amplify lib might not be working right to configure the permissions and the api. It Looks like it only sets up what ever i first specify, in this case the cognito user pools permissions, and doesn't do anything with the api key when configuring more. I try and manually set it up in the console, but then i can't update the schema to have a public and owner permission... Not expecting you to help me debug, i'm just wondering what versions you are using and if you happen to run into anything weird like this.
Hey Russ. I'm using @aws-amplify/cli@4.13.4
One thing I ran into is that I was trying to configure the API key by running
amplify add auth
(I figured making stuff public has to do with auth?), while that should be done inamplify add/update api
, but it doesn't sound like that's what's happening to you.Does your generated
aws-exports.js
containaws_appsync_apiKey
? If you're usingamplify mock
it might remove some stuff from there when you shut it down.It actually does, it also has the keys for the cognito user:
aws_user_pools_id
. In youramplify/backend/backend-config.json
file, do you have anapi.<API_NAME>.output.authConfig
property that has values in theadditionalAuthenticationProviders
anddefaultAuthentication
?Yes, I do.
AMAZON_COGNITO_USER_POOLS
is underdefaultAuthentication
andAPI_KEY
is underadditionalAuthenticationProviders