Page updated Mar 29, 2024

Preview: AWS Amplify's new code-first DX (Gen 2)

The next generation of Amplify's backend building experience with a TypeScript-first DX.

Get started

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:

1amplify add auth

Select the defaults for the following prompts:

1? Do you want to use the default authentication and security configuration? Default configuration
2
3Warning: you will not be able to edit these selections.
4? How do you want users to be able to sign in? Username
5? Do you want to configure advanced settings? No, I am done.

To deploy the service, run the push command:

1amplify push
2
3✔ 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:

1amplify 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:

1npm install @aws-amplify/ui-react

Add the Amplify UI Authenticator component

Open src/App.tsx or src/App.jsx and make the following changes:

  1. Import the withAuthenticator component:
1import { withAuthenticator, Button, Heading } from '@aws-amplify/ui-react';
2import '@aws-amplify/ui-react/styles.css';
  1. Pass {signOut, user} to the App component. (For TypeScript, you'll also need to add types for the signOut and user props):
1import { type AuthUser } from "aws-amplify/auth";
2import { type UseAuthenticator } from "@aws-amplify/ui-react-core";
3
4...
5
6type AppProps = {
7 signOut?: UseAuthenticator["signOut"]; //() => void;
8 user?: AuthUser;
9};
10
11...
12
13const App: React.FC<AppProps> = ({ signOut, user }) => {
14 // ...
15};
1const App = ({ signOut, user }) => {
2 // ...
3};
  1. Add this heading and button block to the top of your App component:
1return (
2 <div style={styles.container}>
3 <Heading level={1}>Hello {user.username}</Heading>
4 <Button onClick={signOut}>Sign out</Button>
5 <h2>Amplify Todos</h2>
6 ...
7 </div>
8);
  1. Lastly, wrap your App export with the withAuthenticator Amplify UI component:
1export default withAuthenticator(App);

Run the app to see the new authentication flow protecting the app:

1npm 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:

1import { useEffect, useState } from "react";
2
3import { generateClient } from "aws-amplify/api";
4
5import { createTodo } from "./graphql/mutations";
6import { listTodos } from "./graphql/queries";
7import { type CreateTodoInput, type Todo } from "./API";
8
9import { withAuthenticator, Button, Heading } from "@aws-amplify/ui-react";
10import { type AuthUser } from "aws-amplify/auth";
11import { type UseAuthenticator } from "@aws-amplify/ui-react-core";
12import "@aws-amplify/ui-react/styles.css";
13
14const initialState: CreateTodoInput = { name: "", description: "" };
15const client = generateClient();
16
17type AppProps = {
18 signOut?: UseAuthenticator["signOut"]; //() => void;
19 user?: AuthUser;
20};
21
22const App: React.FC<AppProps> = ({ signOut, user }) => {
23 const [formState, setFormState] = useState<CreateTodoInput>(initialState);
24 const [todos, setTodos] = useState<Todo[] | CreateTodoInput[]>([]);
25
26 useEffect(() => {
27 fetchTodos();
28 }, []);
29
30 async function fetchTodos() {
31 try {
32 const todoData = await client.graphql({
33 query: listTodos,
34 });
35 const todos = todoData.data.listTodos.items;
36 setTodos(todos);
37 } catch (err) {
38 console.log("error fetching todos");
39 }
40 }
41
42 async function addTodo() {
43 try {
44 if (!formState.name || !formState.description) return;
45 const todo = { ...formState };
46 setTodos([...todos, todo]);
47 setFormState(initialState);
48 await client.graphql({
49 query: createTodo,
50 variables: {
51 input: todo,
52 },
53 });
54 } catch (err) {
55 console.log("error creating todo:", err);
56 }
57 }
58
59 return (
60 <div style={styles.container}>
61 <Heading level={1}>Hello {user?.username}</Heading>
62 <Button onClick={signOut}>Sign out</Button>
63 <h2>Amplify Todos</h2>
64 <input
65 onChange={(event) =>
66 setFormState({ ...formState, name: event.target.value })
67 }
68 style={styles.input}
69 value={formState.name}
70 placeholder="Name"
71 />
72 <input
73 onChange={(event) =>
74 setFormState({ ...formState, description: event.target.value })
75 }
76 style={styles.input}
77 value={formState.description as string}
78 placeholder="Description"
79 />
80 <button style={styles.button} onClick={addTodo}>
81 Create Todo
82 </button>
83 {todos.map((todo, index) => (
84 <div key={todo.id ? todo.id : index} style={styles.todo}>
85 <p style={styles.todoName}>{todo.name}</p>
86 <p style={styles.todoDescription}>{todo.description}</p>
87 </div>
88 ))}
89 </div>
90 );
91};
92
93const styles = {
94 container: {
95 width: 400,
96 margin: "0 auto",
97 display: "flex",
98 flexDirection: "column",
99 justifyContent: "center",
100 padding: 20,
101 },
102 todo: { marginBottom: 15 },
103 input: {
104 border: "none",
105 backgroundColor: "#ddd",
106 marginBottom: 10,
107 padding: 8,
108 fontSize: 18,
109 },
110 todoName: { fontSize: 20, fontWeight: "bold" },
111 todoDescription: { marginBottom: 0 },
112 button: {
113 backgroundColor: "black",
114 color: "white",
115 outline: "none",
116 fontSize: 18,
117 padding: "12px 0px",
118 },
119} as const;
120
121export default withAuthenticator(App);
1import { useEffect, useState } from 'react';
2
3import { generateClient } from 'aws-amplify/api';
4
5import { createTodo } from './graphql/mutations';
6import { listTodos } from './graphql/queries';
7
8import { withAuthenticator, Button, Heading } from '@aws-amplify/ui-react';
9import '@aws-amplify/ui-react/styles.css';
10
11const initialState = { name: '', description: '' };
12const client = generateClient();
13
14const App = ({ signOut, user }) => {
15 const [formState, setFormState] = useState(initialState);
16 const [todos, setTodos] = useState([]);
17
18 useEffect(() => {
19 fetchTodos();
20 }, []);
21
22 function setInput(key, value) {
23 setFormState({ ...formState, [key]: value });
24 }
25
26 async function fetchTodos() {
27 try {
28 const todoData = await client.graphql({
29 query: listTodos
30 });
31 const todos = todoData.data.listTodos.items;
32 setTodos(todos);
33 } catch (err) {
34 console.log('error fetching todos');
35 }
36 }
37
38 async function addTodo() {
39 try {
40 if (!formState.name || !formState.description) return;
41 const todo = { ...formState };
42 setTodos([...todos, todo]);
43 setFormState(initialState);
44 await client.graphql({
45 query: createTodo,
46 variables: {
47 input: todo
48 }
49 });
50 } catch (err) {
51 console.log('error creating todo:', err);
52 }
53 }
54
55 return (
56 <div style={styles.container}>
57 <Heading level={1}>Hello {user.username}</Heading>
58 <Button onClick={signOut} style={styles.button}>
59 Sign out
60 </Button>
61 <h2>Amplify Todos</h2>
62 <input
63 onChange={(event) => setInput('name', event.target.value)}
64 style={styles.input}
65 value={formState.name}
66 placeholder="Name"
67 />
68 <input
69 onChange={(event) => setInput('description', event.target.value)}
70 style={styles.input}
71 value={formState.description}
72 placeholder="Description"
73 />
74 <button style={styles.button} onClick={addTodo}>
75 Create Todo
76 </button>
77 {todos.map((todo, index) => (
78 <div key={todo.id ? todo.id : index} style={styles.todo}>
79 <p style={styles.todoName}>{todo.name}</p>
80 <p style={styles.todoDescription}>{todo.description}</p>
81 </div>
82 ))}
83 </div>
84 );
85};
86
87const styles = {
88 container: {
89 width: 400,
90 margin: '0 auto',
91 display: 'flex',
92 flexDirection: 'column',
93 justifyContent: 'center',
94 padding: 20
95 },
96 todo: { marginBottom: 15 },
97 input: {
98 border: 'none',
99 backgroundColor: '#ddd',
100 marginBottom: 10,
101 padding: 8,
102 fontSize: 18
103 },
104 todoName: { fontSize: 20, fontWeight: 'bold' },
105 todoDescription: { marginBottom: 0 },
106 button: {
107 backgroundColor: 'black',
108 color: 'white',
109 outline: 'none',
110 fontSize: 18,
111 padding: '12px 0px'
112 }
113};
114
115export 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 inputs with TextFields and the divs with Views.

  1. Add the Text, TextField, View components to the imported components from Amplify UI:
1import {
2 withAuthenticator,
3 Button,
4 Heading,
5 Text,
6 TextField,
7 View
8} from '@aws-amplify/ui-react';
  1. Replace the p tags with Text, the inputs with TextFields and the divs with Views in your App component:
1<View style={styles.container}>
2 <Heading level={1}>Hello {user.username}</Heading>
3 <Button style={styles.button} onClick={signOut}>
4 Sign out
5 </Button>
6 <Heading level={2}>Amplify Todos</Heading>
7 <TextField
8 placeholder="Name"
9 onChange={(event) => setInput('name', event.target.value)}
10 style={styles.input}
11 defaultValue={formState.name}
12 />
13 <TextField
14 placeholder="Description"
15 onChange={(event) => setInput('description', event.target.value)}
16 style={styles.input}
17 defaultValue={formState.description}
18 />
19 <Button style={styles.button} onClick={addTodo}>
20 Create Todo
21 </Button>
22 {todos.map((todo, index) => (
23 <View key={todo.id ? todo.id : index} style={styles.todo}>
24 <Text style={styles.todoName}>{todo.name}</Text>
25 <Text style={styles.todoDescription}>{todo.description}</Text>
26 </View>
27 ))}
28</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.