Front-End Web & Mobile

Use existing Cognito resources for your Amplify API, Storage and more

This post was written by Rene Brandel, Senior Product Manager, AWS

In this guide you will learn how to integrate your existing Cognito User Pool & Federated Identities (Identity Pool) into an Amplify project. This will enable your GraphQL API (AppSync), Storage (S3) and other resources to leverage your existing authentication mechanism.

AWS Amplify is the fastest and easiest way to build cloud-powered mobile and web apps on AWS. Amplify comprises a set of tools and services that enables front-end web and mobile developers to leverage the power of AWS services to build innovative and feature-rich applications.

With today’s Amplify CLI release, customers can re-use their existing Cognito resources for their Amplify project.

Benefits:

  • enable Amplify categories (API, storage, function etc.) for your existing user base
  • incrementally adopt Amplify for your application stack
  • independently manage Cognito resources while working with Amplify

What we’ll learn:

  • How to use your existing Cognito resource with the Amplify JS Library’s Authenticator UI component
  • How to authorize your existing users to interface with a new GraphQL API
  • How to use your existing Identity Pool to manage file storage authorization with S3

What we’ll build:

  • A todo application backed by an existing Cognito User Pool.
  • Every user gets to set their own profile picture only visible only to themselves.

Pre-requisites:

  • Install the latest Amplify CLI version
    • Open terminal and run npm install -g @aws-amplify/cli to update to the latest Amplify CLI.
  • Amplify CLI is already configured
    • If you haven’t configured the Amplify CLI yet, follow this guide on our documentation page.
  • Have an existing Cognito User Pool and Identity Pool
    • Make sure your Cognito User Pool has at least two app clients with the following configuration:
      • one app client without a client secret
      • one app client with a client secret
    • You’ll require an Identity Pool with the both of aforementioned User Pool app clients as an authentication provider in order to complete the storage integration showcased at the bottom of the article.

Create a Cognito User Pool & Identity Pool outside of your Amplify project

You can skip this section if you already have an existing User Pool & Identity Pool.
Go to the AWS Console to create a Cognito User Pool. For the purposes of this blog, I’ll create a User Pool called “ExistingUserPool” with the default settings and added a test user to it.

Make sure to create two app clients:

  • “Web app client” – an app client without a client secret
  • “Native app client” – an app client with a client secret

Now let’s create a new Identity Pool and use both app clients from “ExistingUserPool” as an authentication provider. I’ll name this IdentityPool “ExistingIdentityPool”.

Setup React & Amplify project

Run the following command to create a new React project called “photo-share”

npx create-react-app amplify-todo
cd amplify-todo

Initialize an Amplify project by running:

amplify init

Import Cognito resources

To import your Cognito resources run:

amplify import auth

Upon successful completion of the “import” command:

  • Amplify Library’s configuration file with your Cognito resource information will be generated upon the next “amplify push”;
  • Any other category (api, storage and more) can leverage the imported Cognito resource as an authentication & authorization mechanism;
  • Ability to configure other categories to access the imported resource. For instance, a Lambda function (“amplify add function”) can access this auth resource.

Add Login to your React app

Now that we’ve imported our Cognito resource into our Amplify project, let’s start to leverage it within our UI. Let’s generate the configuration files by running:

amplify push

Accept all the default values for the prompted questions.

You should see a new “aws-exports.js” file in the “src/” folder with the Cognito resource information. Your aws-exports.js file should look something like this:

const awsmobile = {
  "aws_project_region": "YOUR_REGION",
  "aws_cognito_identity_pool_id": "YOUR_IDENTITY_POOL_ID",
  "aws_cognito_region": "YOUR_REGION",
  "aws_user_pools_id": "YOUR_USER_POOL_ID",
  "aws_user_pools_web_client_id": "YOUR_USER_POOL_WEB_CLIENT_ID",
  "oauth": {}
};

In order to take advantage of the imported Cognito resource. Let’s start adding a login experience around our application. First install the Amplify JS library and its React UI component:

yarn add aws-amplify @aws-amplify/ui-react

Once the dependencies are installed, we have to configure your app to pick up the Amplify configuration generated by amplify push. Do that by adding the following lines to your index.js file:

import { Amplify } from 'aws-amplify';
import awsconfig from './aws-exports';

Amplify.configure(awsconfig)

Now, we need to update we need to wrap our App component with the higher order component withAuthenticator in the App.js file as shown below. This will automatically configure the UI component to interface with our imported Cognito User Pool.

Add the withAuthenticator to your imports in App.js:

import { withAuthenticator } from '@aws-amplify/ui-react'

Modify the export default statement on the very bottom of App.js

export default withAuthenticator(App)

Let’s build our app and test the login experience by running:

yarn start

Amplify Authenticator UI components with an imported Cognito User Pool

? You should now see a login screen and signing in with an existing user should work right out-of-the-box.

Add a GraphQL API with Cognito User Pool as an auth mechanism

Now that we’ve got our login experience completed, let’s move to creating a new GraphQL API backed by our existing Cognito resource. Run the following command:

amplify add api
? Please select from one of the below mentioned services:
> GraphQL
? Provide API name:
> amplifytodo

Make sure you select “Cognito User Pool” as the default authorization type:

? Choose the default authorization type for the API
> Amazon Cognito User Pool

Select the default options for the remaining questions:

? Do you want to configure advanced settings for the GraphQL API
> No, I am done.
? Do you have an annotated GraphQL schema?
> No
? Choose a schema template:
> Single object with fields (e.g., “Todo” with ID, name, description)

Let’s configure the authorization modes for your GraphQL schema. Edit the GraphQL schema and add the following code to configure “owner-based” authorization.

? Do you want to edit the schema now
> Yes

Owner-based authorization assigns a model an owner. By default, the owner is the creator of the object. With the authorization rule below, an owner can only create, read, update, and delete their own Todo items.

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

Deploy your new API & generate the client-side GraphQL mutation & query code by running:

amplify push

Let’s tweak your App component to create a simple “todo” app. Replace your App.js file with the following content:

/* src/App.js */
import React, { useEffect, useState } from 'react'
import { withAuthenticator } from '@aws-amplify/ui-react';
import { API, graphqlOperation } from 'aws-amplify'
import { createTodo } from './graphql/mutations'
import { listTodos } from './graphql/queries'

const initialState = { name: '', description: '' }

const App = () => {
  const [formState, setFormState] = useState(initialState)
  const [todos, setTodos] = useState([])

  useEffect(() => {
    fetchTodos()
  }, [])

  function setInput(key, value) {
    setFormState({ ...formState, [key]: value })
  }

  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') }
  }

  async function addTodo() {
    try {
      if (!formState.name || !formState.description) return
      const todo = { ...formState }
      setTodos([...todos, todo])
      setFormState(initialState)
      await API.graphql(graphqlOperation(createTodo, {input: todo}))
    } catch (err) {
      console.log('error creating todo:', err)
    }
  }

  return (
    <div style={styles.container}>
      <h2>Amplify Todos</h2>
      <input
        onChange={event => setInput('name', event.target.value)}
        style={styles.input}
        value={formState.name} 
        placeholder="Name"
      />
      <input
        onChange={event => setInput('description', event.target.value)}
        style={styles.input}
        value={formState.description}
        placeholder="Description"
      />
      <button style={styles.button} onClick={addTodo}>Create Todo</button>
      {
        todos.map((todo, index) => (
          <div key={todo.id ? todo.id : index} style={styles.todo}>
            <p style={styles.todoName}>{todo.name}</p>
            <p style={styles.todoDescription}>{todo.description}</p>
          </div>
        ))
      }
    </div>
  )
}

const styles = {
  container: { width: 400, margin: '0 auto', display: 'flex', flex: 1, flexDirection: 'column', justifyContent: 'center', padding: 20 },
  todo: {  marginBottom: 15 },
  input: { border: 'none', backgroundColor: '#ddd', marginBottom: 10, padding: 8, fontSize: 18 },
  todoName: { fontSize: 20, fontWeight: 'bold' },
  todoDescription: { marginBottom: 0 },
  button: { backgroundColor: 'black', color: 'white', outline: 'none', fontSize: 18, padding: '12px 0px' },
  hr: { width: '100%', height: 1 }
}

export default withAuthenticator(App)

Let’s run your app (yarn start) and try opening your app also in an Incognito window where you can login with a different account to see the owner-based authorization in action.

Showcase of owner-based authentication working

?We’ve got a new Amplify API up and running using your existing Cognito User Pool as an authorization mechanism!

Add a Storage resource to store profile photos

Let’s enable our customers to set their own profile pictures. We’ll store the profile pictures in a newly provisioned S3 bucket and use an existing Identity Pool as a mechanism to authorize the profile photo access.

Run the following command in your project folder:

amplify add storage

Respond with the following answers:

? Please select from one of the below mentioned services:
> Content (Images, audio, video, etc.)
? Please provide a friendly name for your resource that will be used to label this category in the project:
> (Accept default)
? Please provide bucket name: 
> (Accept default)
? Who should have access:
> Auth users only
? What kind of access do you want for Authenticated users?
> create/update, read, delete
? Do you want to add a Lambda Trigger for your S3 Bucket? 
> No

Note: Don’t forget to use the “space bar” to select all three permissions “Create/Update”, “Read” and “Delete”.

Once you’re done adding the storage resource, deploy them to the cloud by running:

amplify push

Add app logic to upload & view profile photo

With our backend ready, we can start adding some UI code to upload a new profile picture and view the profile picture once uploaded.

Import Storage as an imported module from aws-amplify. Also import the AmplifyS3Image component from @aws-amplify/ui-react

import { API, graphqlOperation, Storage } from 'aws-amplify'
import { withAuthenticator, AmplifyS3Image } from '@aws-amplify/ui-react';

In your App component’s render function, add some new UI elements to display and upload the profile photo. Place these right below the <h2>Amplify Todos</h2> header.

<div>Profile picture</div>
<input
    type="file" accept='image/png'
    onChange={(e) => uploadPhoto(e.target.files[0])}
/>
<AmplifyS3Image level='private' imgKey={'profile.png'} />
<hr style={styles.hr} />

Finally, let’s add the uploadPhoto function to our App component:

async function uploadPhoto(file) {
  Storage.put('profile.png', file, {
    contentType: 'image/png',
    level: 'private'
  })
    .then(result => {
     console.log(result)
     // for the sake of the demo, force a reload to see the uploaded picture
     window.location.reload()
  })
    .catch(err => console.log(err));
 }

You should now be able to upload your profile picture. Your finished app should look something like this:
Showcase of the imported Identity Pool with Amplify's storage category

?Success!

Congratulations! You’ve got a fully functioning Amplify project up and running with your existing Cognito User Pool. With a few commands, you were able to build out authentication, API and storage experiences for your existing user base.

Next steps: If you’re looking for more information like multi-environment support and how to unlink an imported resource, please review our documentation. If you have any feedback or enhancement requests, please create an issue in the Github repository.