Multiple Authentication Types in AWS Amplify

·

4 min read

The goal is to create a backend that allows us to:

  1. Signup and login
  2. Create and manage posts through a GraphQL API
  3. Enable everyone (also unauthenticated users) to READ all posts
  4. 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 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:

  1. aws-exports.js - A configuration file for our frontend to communicate with our backend
  2. 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.