Front-End Web & Mobile

AWS Amplify allows you to mix and match authorization modes in DataStore

With today’s release, Amplify DataStore gains the ability to configure multiple authorization modes for a single app data backend. DataStore provides frontend app developers the ability to build real-time apps with offline capabilities by storing data on-device (web browser or mobile device), and automatically synchronizing data to the cloud and across devices on an internet connection. Customers can configure authorization rules visually using Amplify Admin UI or by editing a GraphQL schema file using the Amplify CLI.

What we’ll learn:

  • How to configure authorization rules with Amplify Admin UI to authorize data access for anyone, any signed-in user, specific users, and user groups.
  • How authorization rules are evaluated by DataStore when multiple authorization types are specified

What we’ll build:

  • A React-based social media app with public read-only content such as posts
  • Signed-in user can add new posts
  • Administrators can delete content from the feed

Prerequisites:

  • Install the latest Amplify CLI version by running npm install -g @aws-amplify/cli in your terminal.

1. Deploy the sample project

To accelerate this project, deploy this sample repository using the button below.


Deploy to Amplify Console

Once deployed, you should see your app up and running. At the current stage, all app data is completely public and authorization restrictions aren’t in-place. This means any user can do create, read, update, and delete any data in this app backend.

Try it for yourself. Notice how you didn’t need to log in and could already create a new blog post?

While you’re playing around with this sample app, try logging in to the app. Email-based authentication is all set up already!

Let’s go ahead and create a new user:

Screenshot of User Creation workflow

2. Configure authorization rules in the backend

Next, let’s set up authorization rules in the backend to protect our app data to with the following access patterns. In our case, we have the following rules:

  • Anyone can read any posts
  • Any signed-in user can create, read, update, or delete their own posts
  • Any user that are part of the “Admins” group is allowed to delete any posts from the platform (i.e. content moderators)

We’ll look at these rules one-by-one and set them up via the Amplify Admin UI.

Go to your Amplify app and enable click “Enable Admin UI”. This will provide you a visual interface to configure your backend.

Once enabled, click “Open Admin UI” to view your app backend’s data model. For the purposes of this demo, the backend already includes a “Post” model and a deployed authentication service for email-based login.

Click on “Data“ to view your app’s data model and then select “Post” to reveal all authorization rules in the inspector panel on the right-hand side.

Screenshot of Admin UI's authorization panel

Rule A: Anyone can read any posts

In this rule, we need to specify that anyone (i.e. unauthenticated customers) can only have read access to posts. Therefore, we need to disallow create, update, and delete operations for “Anyone authenticated with API key”.

All we need to do is uncheck the create, update, and delete operations.

You might be asking yourself: “What does ‘authenticated with API Key’ mean?”

Developers can choose how public/unauthenticated users communicate with their backend. By default, Amplify uses an API Key for public/unauthenticated data access but Amplify also supports IAM-based authorization for unauthenticated data access.

In general, it is recommended to use API Key for getting started or prototyping. For production, IAM-based authorization is preferred because of its increased security controls and lack of an expiry date like API keys. Review the AWS_IAM Authorization section on the AWS AppSync documentation to learn more.

Rule B: Any signed-in user can create and delete their own posts

Next, we need to introduce the concept of “ownership” to our app data. Amplify provides “owner-based” authorization, which allows you to tie a data record to a specific user, or “owner”. Owners of a record will automatically have create, read, update, and delete access to the record.

When owner-based authorization is enabled, then an authenticated user is automatically assigned as a record’s owner upon record creation. For example, if “Rene creates a new post XYZ”, then XYZ’s “owner“ is Rene.

Owners can explicitly deny other users access to their “owned“ records. In this case, we’ll want owners to deny other users to delete and update their record but anyone should still be able to read them. Go ahead and toggle ”Enable owner authorization“ and uncheck ”Read“ to deny other users only Update and Delete access.

Rule C: Users of the “Admins” user group are allowed to delete any post from the platform

Amplify also supports authorization rules for users who are part of “user groups”. For our use case, we should have an “Admins” group that have permission to delete any posts. Let’s create a new user group and then provide it delete permissions.

Perfect! We’ve got all the backend related changes ready! Let’s hit “Save and deploy” to deploy our changes to the cloud.

Users can be assigned manually to user groups via the Admin UI’s “user management” capability or programmatically modified using “Admin actions API”. For this demo, I’ll use the Admin UI to add my user to the “Admins” user group.

3. Configure frontend changes

Once deployment is successful, let’s pull the latest changes to my local setup. If you haven’t already, clone the React code base and run amplify pull to fetch all the latest configurations.

Create sample data for testing purposes

Let’s add some sample data to our app, so it doesn’t look so empty! Go to the Admin UI’s “Content” tab and select “Actions > Auto-generate data”. Now generate 10 records for our testing purposes.

Because DataStore is a real-time system, you can actually see the newly generated records automatically pop up in your deployed sample app.

Enable DataStore to use multiple authorization types

Unless otherwise specified, DataStore uses your API’s default authorization type specified in the aws-exports.js file or in the “Manage authorization mode & keys” section of the Admin UI.

In order to take advantage of multiple different authorization types within the same app, you need to change DataStore’s authModeStrategyType.

Go to the index.js file and update the Amplify configuration code to use AuthModeStrategyType.MULTI_AUTH. Your Amplify.configure(...) function should look like this:

Amplify.configure({
  ...awsconfig,
  DataStore: {
    authModeStrategyType: AuthModeStrategyType.MULTI_AUTH
  }
})

That’s it! Functionally, everything will work according to the authorization rules.

4. Verify authorization rules

DataStore provides a “local programming model” and works out-of-the-box offline as well. This means that any changes to the data set is first applied locally and then synced in the background with the backend. For example, if I delete an item that I’m not supposed to have access to, it’ll delete it locally first and then raise an error when deletion wasn’t successful service-side.

Verifying Rule A: Anyone can read (but not create, update, or delete) any posts

To verify the public access behavior. Let’s first make sure we’re logged out (click the button on the upper left corner). Now open “Developer Tools” and go to the “Network” tab.

Try deleting posts or create new posts while logged out. DataStore tries to address these transactions locally but the actual sync to the cloud fails because an unauthenticated user isn’t allowed to create, update, or delete posts.

It is best practice to also prevent these operations in the first place from the UI to limit users from running into unauthorized access patterns. For example, you can ensure that the “Add new post” button only appears when the user is logged in. Modify the “Add new post” button in App.js with the following check:

        {currentUser &&
          <button onClick={() => {
            DataStore.save(new Post({
              content: window.prompt('New post:')
            }))
          }}>
            📝 Add a new post
          </button>
        }

You can also dynamically remove the “Delete” button on a post by adding the currentUser check in the PostView.js as well:

    let showDeleteButton = false

    if (currentUser) {
        showDeleteButton = true
    }

This snippet ensures that by default that the user is not seeing the delete button and only if the “currentUser” is authenticated, then show the delete button.

Verifying Rule B: Any signed-in user can create, read, update, and delete their own posts (but others can’t update and delete these posts)

To verify this access patterns, let’s make sure we’re logged in with an account that’s not part of the Admins group (click the “log in” button on the upper left corner). Try deleting a record that wasn’t created by this account; for example an auto-generated record by Admin UI. The “Network” tab should show you unauthorized requests again.

Next, we should create a visual indicator if a post was created by “me” vs. created by others.

When you enable “owner-based” authorization, an “owner” field is automatically added to each record. The owner field is assigned the record creator’s Amazon Cognito user pool “sub”, a unique user identifier. We should only show the “delete” button on a post if it was created by me. Let’s edit PostView.js to reflect that:

    if (currentUser) {
        showDeleteButton = currentUser.attributes.sub === post.owner
    }

Verifying Rule C: Any user that are part of the “Admins” group is allowed to delete any post from the platform

Now, log in to a user that’s part of the Admins group. You’ll see a problem here.

Because of our changes above, we don’t have “delete” buttons on posts even though Admins are allowed to delete posts.

We need to add another condition to the “PostView.js” component to show the “delete” button on any posts if the user is part of the “Admins” group:

    if (currentUser) {
        showDeleteButton = currentUser.attributes.sub === post.owner
        || currentUser.signInUserSession.accessToken.payload['cognito:groups'].includes('Admins')
    }

To verify that the deletions are successful, you can look at the Network tab again or the GraphQL requests complete without any “Unauthorized” errors.

🥳 Success!

In this blog post, you’ve learned how to:

  1. configure your Amplify DataStore to enable multiple authorization types and rules.
  2. set up authorization rules on your backend correctly
  3. create frontend / UI updates to prevent users to initiate unauthorized requests

This only scratches the surface of what you can achieve with AWS Amplify. For more information on Amplify DataStore and authorization rules review: