Add authentication
The next feature you will be adding is authentication.
Authentication with Amplify
Amplify uses Amazon Cognito as its authentication provider. Amazon Cognito is a robust user directory service that handles user registration, authentication, account recovery & other operations. In this tutorial, you'll learn how to add authentication to your application using Amazon Cognito and username/password login.
Create authentication service
To add authentication to your app, run this command:
amplify add auth
Select the defaults for the following prompts:
? Do you want to use the default authentication and security configuration? Default configuration
Warning: you will not be able to edit these selections.? How do you want users to be able to sign in? Username? Do you want to configure advanced settings? No, I am done.
To deploy the service, run the push
command:
amplify push
✔ Are you sure you want to continue? (Y/n) · yes
Now, the authentication service has been deployed and you can start using it. To view the deployed services in your project at any time, go to Amplify Console by running the following command:
amplify console
Create login UI
Now that you have your authentication service deployed to AWS, it's time to add authentication to your app. Creating a login flow can be quite difficult and time consuming to get right. Luckily, Amplify UI has an Authenticator
component that provides an entire authentication flow for you, using the configuration you specified in amplifyconfiguration.json.
Install Amplify UI
The @aws-amplify/ui-react
package includes React specific UI components you'll use to build your app. Install it with the following command:
npm install @aws-amplify/ui-react
Add the Amplify UI Authenticator component
Open src/App.tsx or src/App.jsx and make the following changes:
- Import the
withAuthenticator
component:
import { withAuthenticator, Button, Heading } from '@aws-amplify/ui-react';import '@aws-amplify/ui-react/styles.css';
- Pass
{signOut, user}
to theApp
component. (For TypeScript, you'll also need to add types for thesignOut
anduser
props):
import { type AuthUser } from "aws-amplify/auth";import { type UseAuthenticator } from "@aws-amplify/ui-react-core";
...
type AppProps = { signOut?: UseAuthenticator["signOut"]; //() => void; user?: AuthUser;};
...
const App: React.FC<AppProps> = ({ signOut, user }) => { // ...};
const App = ({ signOut, user }) => { // ...};
- Add this heading and button block to the top of your
App
component:
return ( <div style={styles.container}> <Heading level={1}>Hello {user.username}</Heading> <Button onClick={signOut}>Sign out</Button> <h2>Amplify Todos</h2> ... </div>);
- Lastly, wrap your
App
export with thewithAuthenticator
Amplify UI component:
export default withAuthenticator(App);
Run the app to see the new authentication flow protecting the app:
npm run dev
Now you should see the app load with an authentication flow allowing users to sign up and sign in.
Review complete code example
Here's all the code below:
import { useEffect, useState } from "react";
import { generateClient } from "aws-amplify/api";
import { createTodo } from "./graphql/mutations";import { listTodos } from "./graphql/queries";import { type CreateTodoInput, type Todo } from "./API";
import { withAuthenticator, Button, Heading } from "@aws-amplify/ui-react";import { type AuthUser } from "aws-amplify/auth";import { type UseAuthenticator } from "@aws-amplify/ui-react-core";import "@aws-amplify/ui-react/styles.css";
const initialState: CreateTodoInput = { name: "", description: "" };const client = generateClient();
type AppProps = { signOut?: UseAuthenticator["signOut"]; //() => void; user?: AuthUser;};
const App: React.FC<AppProps> = ({ signOut, user }) => { const [formState, setFormState] = useState<CreateTodoInput>(initialState); const [todos, setTodos] = useState<Todo[] | CreateTodoInput[]>([]);
useEffect(() => { fetchTodos(); }, []);
async function fetchTodos() { try { const todoData = await client.graphql({ query: 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 client.graphql({ query: createTodo, variables: { input: todo, }, }); } catch (err) { console.log("error creating todo:", err); } }
return ( <div style={styles.container}> <Heading level={1}>Hello {user?.username}</Heading> <Button onClick={signOut}>Sign out</Button> <h2>Amplify Todos</h2> <input onChange={(event) => setFormState({ ...formState, name: event.target.value }) } style={styles.input} value={formState.name} placeholder="Name" /> <input onChange={(event) => setFormState({ ...formState, description: event.target.value }) } style={styles.input} value={formState.description as string} 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", 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", },} as const;
export default withAuthenticator(App);
import { useEffect, useState } from 'react';
import { generateClient } from 'aws-amplify/api';
import { createTodo } from './graphql/mutations';import { listTodos } from './graphql/queries';
import { withAuthenticator, Button, Heading } from '@aws-amplify/ui-react';import '@aws-amplify/ui-react/styles.css';
const initialState = { name: '', description: '' };const client = generateClient();
const App = ({ signOut, user }) => { 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 client.graphql({ query: 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 client.graphql({ query: createTodo, variables: { input: todo } }); } catch (err) { console.log('error creating todo:', err); } }
return ( <div style={styles.container}> <Heading level={1}>Hello {user.username}</Heading> <Button onClick={signOut} style={styles.button}> Sign out </Button> <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', 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' }};
export default withAuthenticator(App);
Bonus: Use Amplify UI Primitives
You used two Amplify UI components, Heading
and Button
. You could also convert the rest of the app to Amplify UI components by replacing the p
tags with Text
, the input
s with TextField
s and the div
s with View
s.
- Add the
Text
,TextField
,View
components to the imported components from Amplify UI:
import { withAuthenticator, Button, Heading, Text, TextField, View} from '@aws-amplify/ui-react';
- Replace the
p
tags withText
, theinput
s withTextField
s and thediv
s withView
s in yourApp
component:
<View style={styles.container}> <Heading level={1}>Hello {user.username}</Heading> <Button style={styles.button} onClick={signOut}> Sign out </Button> <Heading level={2}>Amplify Todos</Heading> <TextField placeholder="Name" onChange={(event) => setInput('name', event.target.value)} style={styles.input} defaultValue={formState.name} /> <TextField placeholder="Description" onChange={(event) => setInput('description', event.target.value)} style={styles.input} defaultValue={formState.description} /> <Button style={styles.button} onClick={addTodo}> Create Todo </Button> {todos.map((todo, index) => ( <View key={todo.id ? todo.id : index} style={styles.todo}> <Text style={styles.todoName}>{todo.name}</Text> <Text style={styles.todoDescription}>{todo.description}</Text> </View> ))}</View>
Using Amplify UI connected components makes it easier to manage styling across your entire app.
In this example, you used the Amplify UI library and the withAuthenticator
Higher-Order Component to quickly get up and running with a real-world authentication flow. You can also customize this component to add or remove fields, update styling, or other configurations. You can even override function calls if needed. To learn more, visit the Amplify UI documentation website.
In addition to withAuthenticator
, you can build custom authentication flows with the Amplify Library for JS. Amplify's Auth package has several methods including signUp
, signIn
, forgotPassword
, and signOut
that allow you full control over all aspects of the user authentication flow.