An In-Depth Guide on Amplify GraphQL API Authorization
Published: 2022-01-12 14:57:52

AWS Amplify Auth gives a simple plug and play authentication and authorization experience that can be extended into advanced flows.

The terms authentication and authorization can get confusing. The line that divides them is blurry. For simplicity, when you see the word *Auth*, I am referring to both authentication and authorization. When I need to refer to them individually, I will spell out the full name.


## Getting Started

Before you can set up auth, you need to have an Amplify project. An Amplify project can take different shapes but let’s assume throughout the rest of this article that a project is:


1. A React (can be any framework) project that you have set up with `create-react-app`
2. And that you have run `amplify init` on the project

Regardless of how you set up your project, you should be able to enjoy this article as long as you’ve initialized an Amplify project with `amplify init`. I will be using React but the same principles apply to all other supported frameworks or platforms.

Before we st up Auth, first add an Amplify API to your project. Ultimately, you need Auth to protect your API:

```sh
amplify add api
```

Choose the default options for all questions and wait for the CLI to create the API. You will get a warning that your API is public: 

![](https://paper-attachments.dropbox.com/s_4394877826623BD47395FE3EC9D473E0DD56433429A1EAFEC74C9EBE170833F7_1638771511809_image.png)


That’s because Amplify CLI adds the following line to your `amplify/backend/api/<app name>/schema.graphql`:

```graphql
input AMPLIFY { globalAuthRule: AuthRule = { allow: public } } # FOR TESTING ONLY!
```

This line makes your API public for testing. Comment it out to activate [deny-by-default principle](https://docs.amplify.aws/cli/graphql/authorization-rules/) which we will talk about later in this article.

Now you can add Auth to your project. Run the *add auth* command at the root of your project:

```sh
amplify add auth
```

Amplify will ask you a few questions which it would use to configure Auth. Before you answer any of those questions, let’s walk through those options.


## What Do Your Options Mean?

Here is what you get when you run `amplify add auth`


![image](https://res.craft.do/user/full/7e42be87-6fd0-72dd-8a09-9f750ad65e07/doc/83459368-B246-4E91-B546-8F33BB6AF546/2C6481ED-84E9-42FB-9AEF-D501EE2413FE_2/Image.png)


As a tip, if you are ever stuck making a decision when using the Amplify CLI, you can always select the *I want to learn more* option. For example here is a detailed explanation you get on what the listed options mean:

```txt
This utility allows you to set up Amazon Cognito User Pools and Identity Pools for your a
pplication.

Amazon Cognito User Pool makes it easy for developers to add sign-up and sign-in function
ality to web and mobile applications. It serves as your own identity provider to maintain
  a user directory. It supports user registration and sign-in, as well as provisioning ide
ntity tokens for signed-in users.

Amazon Cognito identity pools provide temporary AWS credentials for users who are guests
(unauthenticated) and for users who have been authenticated and received a token. An iden
tity pool is a store of user identity data specific to your account.

If you choose to use the default configuration, this utility will set up both a Userpool
and an Identity Pool.

If you choose the 'Default configuration with Social Provider (Federation)', the provider
s will be federated with Cognito User Pools.

In either case, User Pools will be federated with Identity Pools allowing any users loggi
ng in to get both identity tokens as well as AWS Credentials.
```

The key takeaways is that you got two primary options:

**Setup a Password-based Authentication (Default Configuration)**

With this option, you users can sign up using username/email and password. You can also customize the form to add more fields like first and last name.

**Setup a Social Login (Default Configuration with Social Provider)**

The word federation might sound intimidating but Amplify is referring to what you might know as Social Login. You can configure Facebook, Google, and all other supported third-party auth providers

You’ll learn how to set up both, starting with the default configuration.


## Set up the Login Screen

Identity is the basic of all kinds of Auth. Being able to confirm that users are who they claim they are is the first stop to protecting their data. Let’s walk through a flow on setting up a password-based authentication using Amplify Auth.

In your Amplify project, run `amplify add auth` and the choose `Default configuration`. Select **Email** and select **No** to advanced settings:


![image](https://res.craft.do/user/full/7e42be87-6fd0-72dd-8a09-9f750ad65e07/doc/83459368-B246-4E91-B546-8F33BB6AF546/A89D8927-C6F5-4E5D-BB84-C638E750C004_2/Image.png)


While you were setting up, Amplify CLI created an `amplify` folder at the root of your project and has been updating it as you ran the `add` commands. The `amplify` folder contains a `backend/auth/<id>/cli-inputs.json`. This is the config file for your auth and if you are ever in doubt about your config settings, look there.

You have configured auth locally but Amplify has not set up anything in the cloud to store user data. Run the following command to ask Amplify to provision all services needed to store and manage user data:

```sh
amplify push
```

Choose the default answers to all the questions and wait for the push to complete.

Amplify offers SDKs that you can take advantage when interacting with your Amplify API. You can install the SDK via npm:

```sh
npm install aws-amplify @aws-amplify/ui-react
```

Open the `src/index.js` file and configure Amplify before rendering the React app:

```js
import Amplify from 'aws-amplify';
import awsconfig from './aws-exports';
Amplify.configure(awsconfig);
```

Amplify added the `./aws-exports` file in your `src` folder during set up.


> Do not edit the `./aws-exports` file. It is automatically generated based on the commands you run using the Amplify CLI

Wrap the React component you want to protect with `Authenticator` and use render props to handle a successful authentication. You can do this to the `src/App.js` component:

```js
import { Authenticator } from "@aws-amplify/ui-react";
import "@aws-amplify/ui-react/styles.css";
export default function App() {
  return (
    <div>
      <Authenticator>
        {({ signOut, user }) => (
          <main>
            <h1>Hello {user.username}</h1>
            <button onClick={signOut}>Sign out</button>
          </main>
        )}
      </Authenticator>
    </div>
  );
}
```   

Start the React app with `npm start` and you should get an authentication form:

![](https://paper-attachments.dropbox.com/s_4394877826623BD47395FE3EC9D473E0DD56433429A1EAFEC74C9EBE170833F7_1638778108718_image.png)

## Identity

Before you can ask a customer for their ID, you need to issue them that ID. You can picture allowing users to create an account as issuing customers IDs.

Click the *Create Account* tab and fill the new account form:


![](https://paper-attachments.dropbox.com/s_4394877826623BD47395FE3EC9D473E0DD56433429A1EAFEC74C9EBE170833F7_1638778126834_image.png)


Click the *CREATE ACCOUNT* button and you should get an email with a confirmation code. Enter the code in the next page and then click *CONFIRM*.


![](https://paper-attachments.dropbox.com/s_4394877826623BD47395FE3EC9D473E0DD56433429A1EAFEC74C9EBE170833F7_1638777897710_image.png)


Now you have access to the app and you can sign out and test the log in page


![](https://paper-attachments.dropbox.com/s_4394877826623BD47395FE3EC9D473E0DD56433429A1EAFEC74C9EBE170833F7_1638778057334_image.png)


Amplify stores your login session in your browser’s local storage. To confirm, open your local storage and look at the data stored for the port your app is running on


![image](https://res.craft.do/user/full/7e42be87-6fd0-72dd-8a09-9f750ad65e07/doc/83459368-B246-4E91-B546-8F33BB6AF546/1A240930-3F49-4441-9AD9-741C16FA51F2_2/Image.png)


You can sign out by clearing this data.


## Private vs Public vs Restricted Data

When you run the `add api` command on an Amplify set up, the schema it generates contains the following line of code:

```graphql
input AMPLIFY { globalAuthRule: AuthRule = { allow: public } } # FOR TESTING ONLY!
```

You can find the schema file in `~/amplify/backend/api/<project name>/schema.graphql`.

**Testing in Public**

The `globalAuthRule` rule allows you to make all your API open to the public when you set the `allow` property to public. It is great for testing but you definitely want to remove it in production. The fact that we've setup authentication does not protect our data yet.


> The fact that we've setup authentication does not protect our data yet

Amplify encourages a [deny-all-first approach](https://docs.amplify.aws/cli/graphql/authorization-rules) so even if you have to make everything available to the public, the best practice is to explicitly declare each of the model as public.

Lets confirm that `globalAuthRule` makes your data public by default. Run the `amplify push` command if you haven’t then run the console command and choose *GraphQL*:

```sh
amplify console api

# Choose GraphQL after running the command
```

This will open the GraphQL queries page so you can test the API. Run the following command and you should get an empty array:

```graphql
query MyQuery {
  listTodos {
    items {
      name
    }
  }
}
```

![](https://paper-attachments.dropbox.com/s_4394877826623BD47395FE3EC9D473E0DD56433429A1EAFEC74C9EBE170833F7_1638779232182_image.png)


If your API was private, you should have gotten a 401 instead.

**Everything is Restricted First**

Remove the following line from your `schema.graphql`:

```graphql
input AMPLIFY { globalAuthRule: AuthRule = { allow: public } } # FOR TESTING ONLY!
```

Run the *push* command to update remote schema:

```sh
amplify push
```

Run `amplify console api` again to open the GraphQL queries page. Run the queries you ran previously and you should get an unauthorized error:


![](https://paper-attachments.dropbox.com/s_4394877826623BD47395FE3EC9D473E0DD56433429A1EAFEC74C9EBE170833F7_1638778925939_image.png)


Even if the user is logged in, they still won’t be able to access the data. Something is restricted if the following 3 are true:


1.  `*globalAuthRule*` is not set to allow public access
2. There is no public rule on a model to make it public to everyone including guests
3. There is no private rule on a model to make it accessible to only signed in users

**Making a Model Public**

To make the Todo model public, you need to annotate its schema with the `@auth` directive:

```graphql
type Todo @model @auth(rules: [{ allow: public }]) {
  id: ID!
  name: String!
  description: String
}
```

Setting the `allow` rule to `public` makes all data represented by this model to be public. That means, `id`, `name`, and `description` data will be public.

Push and test if you can query the todos now:


![](https://paper-attachments.dropbox.com/s_4394877826623BD47395FE3EC9D473E0DD56433429A1EAFEC74C9EBE170833F7_1638779222506_image.png)


You now have a deny-by-default (restricted) API with a public todo API. Your models are now private by default.

In the rest of this post, we will focus on private models so remove the public rule so as to keep the *Todo* model restricted

```graphql
type Todo @model {
  id: ID!
  name: String!
  description: String
}
```



## How to Restrict Access to Owner/Creator

In a Todo app, you want each todo stored in your database to be read, updated, and deleted by ONLY the **creator** or **owner** of the todo. You don’t want user B to access user A’s todos.

A private access level user is just a signed in user. We want to go one step deeper than just signed in users. We want signed in users that created the todo. Here is how to write a schema that matches this rule:

```graphql
type Todo @model 
          @auth(rules: [
            { allow: owner }
          ]) {
  id: ID!
  name: String!
  description: String
}
```

- The model allows only signed in users with the `allow:owner` rule
- This rule also enforces that that only the user who owns a record can create, read, update or delete it

Run the push command to push this change and open the console with `amplify console api`.

You’d still get the Unauthorized error if you try to query for the todos. To see your todos, you have to login. When you add `owner` rules, Amplify will detect it and give you an option to login from the console.

Click the Authorization Provider select box and select the ID of your Cognito User Pool


![](https://paper-attachments.dropbox.com/s_4394877826623BD47395FE3EC9D473E0DD56433429A1EAFEC74C9EBE170833F7_1638779711368_image.png)


When you select the ID, a new *Login with User Pools* button will appear beside the run query button


![](https://paper-attachments.dropbox.com/s_4394877826623BD47395FE3EC9D473E0DD56433429A1EAFEC74C9EBE170833F7_1638779777555_image.png)


Choose the Client ID that matches the `aws_user_pools_web_client_id` property in your `~/src/aws-exports.js` config file.

The username and password should match the email and password you created when you signed up through the React app UI. Remember we are using emails as username.


![](https://paper-attachments.dropbox.com/s_4394877826623BD47395FE3EC9D473E0DD56433429A1EAFEC74C9EBE170833F7_1638780000816_image.png)


Run the query again and you should get an empty array of todos cause you, the user/owner/creator has not created any todos yet


![](https://paper-attachments.dropbox.com/s_4394877826623BD47395FE3EC9D473E0DD56433429A1EAFEC74C9EBE170833F7_1638779939342_image.png)


While you are here, run the following mutations one after the other to add some todos to your app for testing:

```graphql
mutation createTodo {
  createTodo(input: {
    name: "Finish Demo"
    description: "Complete your demo for Amplify Auth"
  }) {
    id
    name
    description
  }
}

mutation createTodo {
  createTodo(input: {
    name: "Outline Article"
    description: "Create outline and send for team review"
  }) {
    id
    name
    description
  }
}

mutation createTodo {
  createTodo(input: {
    name: "Write first draft"
    description: "Write and send draft to your editor for review"
  }) {
    id
    name
    description
  }
}

mutation createTodo {
  createTodo(input: {
    name: "Finish and publish"
    description: "Complete the article and share with your community"
  }) {
    id
    name
    description
  }
}
```

Query the API again with the following to get the list of todos you created:

```graphql
query MyQuery {
  listTodos {
    items {
      name
    }
  }
}
```

**Test Authorization in Client App**

Update your `~/src/App.js` to fetch the signed in user's todo items and render the items on the page:

```js
import React from 'react';
import { withAuthenticator, AmplifySignOut } from '@aws-amplify/ui-react';
import { API, graphqlOperation } from 'aws-amplify'
import { listTodos } from './graphql/queries'

function App ()  {

  const [todos, setTodos] = React.useState([])

  React.useEffect(() => {
    async function fetchTodos() {
      try {
        const todoData = await API.graphql(graphqlOperation(listTodos))
        const todos = todoData.data.listTodos.items

        setTodos(todos)
      } catch (err) { console.log('error fetching todos', err) }
    }

    fetchTodos();
  })

  return (
    <div>
      <AmplifySignOut />
      {todos.map(todo => <p>{todo.name}</p>)}
    </div>
  );
}

export default withAuthenticator(App);
```

When you test in the browser, you will NOT get the list of todos. Instead, you will get an error in the console that you are unauthorized regardless of the fact that you logged in. 


![](https://paper-attachments.dropbox.com/s_4394877826623BD47395FE3EC9D473E0DD56433429A1EAFEC74C9EBE170833F7_1638849844719_image.png)


The reason is that if you inspect the headers for that request, you will not find an authorization header.

*Request headers from localhost:*

![](https://paper-attachments.dropbox.com/s_4394877826623BD47395FE3EC9D473E0DD56433429A1EAFEC74C9EBE170833F7_1638849926850_image.png)


When you run the query from Amplify console like we did earlier, you see that the authorization header is added

*Request headers from API console:*

![](https://paper-attachments.dropbox.com/s_4394877826623BD47395FE3EC9D473E0DD56433429A1EAFEC74C9EBE170833F7_1638850158055_CleanShot+2021-12-07+at+08.08.292x.png)


The reason for this disparity is that when I showed you how to set up Amplify API with `amplify add api`, we chose only the default options. One of those options is the authorization type and it is set to **API Key** by default. 

Owner authorization needs an authorization type that handles identity and authentication. Afterall an owner must be a logged in user first. The authorization type that fits this need is **Amazon Congnito User Pool**. To switch form API Key to Cognito User Pool, run the API update command:

```sh
amplify update api
```

Choose to update **Authorization modes** and then choose Amazon Cognito User Pool as the authorization type:


![](https://paper-attachments.dropbox.com/s_4394877826623BD47395FE3EC9D473E0DD56433429A1EAFEC74C9EBE170833F7_1638850707068_image.png)


The rest of the answers should be default. The `amplify/backend/backend-config.json` will update as well with the config:

```json
"defaultAuthentication": {
    "authenticationType": "AMAZON_COGNITO_USER_POOLS",
},
```

Run the push command to update your servers. Once the push is complete, refresh your app to get your todo list:


![](https://paper-attachments.dropbox.com/s_4394877826623BD47395FE3EC9D473E0DD56433429A1EAFEC74C9EBE170833F7_1638851134733_image.png)



## Allow access to only a group of people

Assuming you want a group of Admin users to have access to a model. It could also be that they are the only group of people that can perform a particular operation like deleting items. Amplify offers two ways to approach this.

**Static Group Authorization**

Using our Todo example, you can allow owners to create, read, update and delete but also allow Admin groups to delete.

```graphql
type Todo @model 
          @auth(rules: [
            { allow: owner },
            { allow: groups, groups: ["Admin"], operations: [delete] }
          ]) {
  id: ID!
  name: String!
  description: String
}
```

**Dynamic Group Authorization**

With dynamic group, you can have a group field in your model that specifies what group can access each record. So you can have shopping related todo items with a group field that has the value of *family*. And then have another set of todo items related to fitness with a group field that has the value of *friends*.

Each record can also have multiple groups which extends the power you have when it comes to assigning roles in your app. Here is an example:

```graphql
# Dynamic group authorization with a single group
type Todo @model @auth(rules: [{ allow: groups, groupsField: "group" }]) {
  id: ID!
  title: String
  group: String
}

# Dynamic group authorization with multiple groups
type Todo @model @auth(rules: [{ allow: groups, groupsField: "groups" }]) {
  id: ID!
  title: String
  groups: [String]
}
```

`groupField` tells Amplify which field in the model represents the group. To add users to a group, refer to [this Cognito guide](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-user-groups.html).

## Field Level Authorization

Sometimes you want the auth rules for a field to be different from the rule that is applied to its parent model.

For instance, if you have a user profile schema that looks like this:

```graphql
type Profile @model @auth(rules: [
  {allow: private}
]) {
  id: ID!
  username: String!
  email: String!
}
```

The model makes sense for a social media app. You want every signed in user (`allow:private`) to view members profile.

It is common though, for users to dislike having their emails accessible to random people. So what if you want to make the email field only accessible to just the owner and not all signed in users?

```graphql
type Profile @model @auth(rules: [
  {allow: private}
]) {
  id: ID!
  username: String!
  email: String! @auth(rules: [{ allow: owner }])
}
```

With this modification, you have a field `email` with a different authorization rule from its parent model, `Profile`. Only the owner of a profile can do anything to their email. Other signed in users can’t.

Field level authorization gives you a lot more power than a model level restriction. You can learn more about them in the [rules docs](https://docs.amplify.aws/cli/graphql-transformer/auth/#field-level-authorization).


## Open ID/Social Authentication

You can take advantage of OAuth and Open ID providers for signing in. Instead of the username/email and password option we’ve seen previously, you can just have a big Sign in with X button on your authentication page.

To set up your Amplify app to authentication with Social, create a new Amplify project and add your API. Next run the Amplify add auth command to start the flow for configuring authentication:

```sh
amplify add auth
```

Instead of choosing the *Default configuration* like you did earlier, choose *Default configuration with Social Provider*:


![image](https://res.craft.do/user/full/7e42be87-6fd0-72dd-8a09-9f750ad65e07/doc/83459368-B246-4E91-B546-8F33BB6AF546/7CF83420-2385-40A9-A0E0-BA3315A3692B_2/Image.png)


You might also want to give the user an option to sign in with either email or social sign in. The next options allows you to pick username/email/etc alongside social:


![image](https://res.craft.do/user/full/7e42be87-6fd0-72dd-8a09-9f750ad65e07/doc/83459368-B246-4E91-B546-8F33BB6AF546/97B1E077-8BA0-40F7-A28E-DA7507B42CA1_2/Image.png)


Choose the default configuration as shown below and enter the redirect URL for your app:


![image](https://res.craft.do/user/full/7e42be87-6fd0-72dd-8a09-9f750ad65e07/doc/83459368-B246-4E91-B546-8F33BB6AF546/93583579-6AFA-4A36-9D72-09C576A92265_2/Image.png)


You’ll also get the option to add multiple redirect URL. This is useful for having both a dev and production URLs as your redirect URL.

The sign out URL is where you want to redirect users after they have signed out of your site:


![image](https://res.craft.do/user/full/7e42be87-6fd0-72dd-8a09-9f750ad65e07/doc/83459368-B246-4E91-B546-8F33BB6AF546/68872A50-AA99-4973-912D-55E8B5220B51_2/Image.png)


Choose a provider for your Social login from the list as shown below (use space bar to choose one):


![image](https://res.craft.do/user/full/7e42be87-6fd0-72dd-8a09-9f750ad65e07/doc/83459368-B246-4E91-B546-8F33BB6AF546/9C0A2E10-F5A3-49CA-98D5-9E18AA86A10D_2/Image.png)


You will be prompted to enter a client ID and secret as shown below:


![image](https://res.craft.do/user/full/7e42be87-6fd0-72dd-8a09-9f750ad65e07/doc/83459368-B246-4E91-B546-8F33BB6AF546/B443AB7A-50B4-4985-BA49-AD9DFF89DC2E_2/Image.png)


You can get this credentials from your Social Provider’s site and the [Social Sign In docs page](https://docs.amplify.aws/lib/auth/social/q/platform/js/) describes in details how to get the credentials. You can also watch [Nader's video](https://www.youtube.com/watch?v=eqDUSY9KHYE) on setting Social login using Google Auth.

Lastly, add a button in your React app to initiate the Social sign in flow:

```js
<button onClick={() => Auth.federatedSignIn({provider: 'Google'})}>Open Google</button>
```

## Is it possible to enable multi factor auth?

Yes! Since Amplify uses Amazon Cognito, it’s easy to allow a user choose how they want to set up Multi-Factor Authentication.

To learn how to set up MFA, refer to the [Amplify MFA](https://docs.amplify.aws/lib/auth/mfa/q/platform/js/) page. If you want to dive deeper into the options a user have for MFA, you can take a look at the [Amazon Cognito MFA docs](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-mfa.html).

You can also use the [Remember Device feature](https://docs.amplify.aws/lib/auth/device_features/q/platform/js/#configure-auth-category) to make the user experience smoother by not always making them go through the MFA process.


## What if a user lost their password?

Remember the sign in page from the Amplify UI library?


![](https://paper-attachments.dropbox.com/s_4394877826623BD47395FE3EC9D473E0DD56433429A1EAFEC74C9EBE170833F7_1638778108718_image.png)



It has a Reset password link which takes you to a new page to reset your password as a user.


![](https://paper-attachments.dropbox.com/s_4394877826623BD47395FE3EC9D473E0DD56433429A1EAFEC74C9EBE170833F7_1639034185407_image.png)


If you are using the UI library, then all you need to do is [customize the UI](https://docs.amplify.aws/ui/auth/authenticator/q/framework/react/#customization) to taste. But if you intend to build your UI from scratch, then you can rely on one of the [authentication methods](https://docs.amplify.aws/lib/auth/manageusers/q/platform/js/#password-operations) that Amplify exposes for resetting passwords


## Build Auth UI from Scratch

If you want to build a custom authentication experience from scratch for your users, you’ve got two options:


1. Customize Amplify UI
2. Use the Authentication methods

You can choose to [move the layout around](https://ui.docs.amplify.aws/components/authenticator#customization) or [change the styles](https://ui.docs.amplify.aws/theming) by customizing Amplify UI. You can get a completely unique user interface with just customizing Amplify UI.

If you need more power than just the looks, you can call the authentication methods manually. For example, if you have a React form with a `username` and `password` controlled inputs, here is the code snippet to use the inputs as a sign up form:

```js
import { Auth } from "aws-amplify";
import React from "react";

async function signUp() {
  const [{ username, password }, setCredentials] = React.useState();
  try {
    const { user } = await Auth.signUp({
      username,
      password,
    });
    console.log(user); // User info
  } catch (error) {
    console.log("error signing up:", error);
  }
}
```

`Auth.signUp` is the important line here. It takes an object of user credentials and returns a promise that can resolve to the user data or reject to an error if the authentication failed.

`Auth.signUp` is just one of many methods. There is also `signIn`, `confirmSignUp`, `signOut`, `setupOTP`, `forgotPassword`, `currentAuthenticatedUser`, etc. All of these methods are documented in the [Authentication library docs](https://docs.amplify.aws/lib/auth/emailpassword/q/platform/js/).


## Conclusion

Feel free to use this article as a reference guide when you are not sure where to find what you are looking for. I left a lot of links in the article for this reason. You have all the fundamentals from what you’ve just learned, but you can go deeper when you need to by referencing the links in the article.