Any application with users requires authentication, but setting up a complete authentication and authorization workflow from scratch could be time-consuming and inefficient.
Strapi enables us to request data for our headless CMS API using multi-authentication providers like Google, Twitter, etc. Making it easier for authenticated users to perform actions only available to authorized users.
The Strapi authentication system allows us to quickly build a robust authentication system for our application. In this tutorial, we will create a simple React application that uses Strapi for authentication and authorization.
The Content Management System (CMS) allows you to create and manage content for your website and applications. In a traditional CMS, the front-end and back-end of the website or application are integrated. Aside from pre-made themes and custom codes, there are no customization options. Traditional CMSes like WordPress, Joomla, and Drupal integrate the website's front-end with the back-end.
A headless CMS lets you build out your client or front-end, connecting it to the CMS via APIs. With a headless CMS, the application front-end can be built using any technology, allowing multiple clients to connect and pull data from the same CMS.
Strapi is the leading open-source, headless, customizable Content Management System. With Strapi, you can easily build custom APIs in REST or GraphQL that can be consumed by any client or front-end framework. We'll be using Strapi API while building authentication and authorization with React.
To authenticate users, Strapi provides two auth options: one social (Google, Twitter, Facebook, etc.) and another local auth (email and Password). Both of these options return a token (JWT token), which will be used to make further requests. JWT token contains user information and expiry which get validated for each request.
For authorization, Strapi has the Roles & Permissions plugin, which allows complete protection of your API by ACL strategy. ACL strategy manages the permissions between the groups of users. Each time an API request is sent, it checks if an Authorization header is present and grabs the JWT token which contains the user ID and verifies if the user making the request has access to the resource.
For this tutorial, we will be using local auth (email and password).
React is a Javascript library for building a user interface with reusable components. In React, you write once and use it everywhere, resulting in less coding and faster development.
React has large community support and it's used by giants like Facebook, Tesla, etc. React provides some outstanding features like JSX (Javascript syntactic extension), Virtual DOM (Document Object Model), etc. Click here to learn more.
At the end of this tutorial, we will have covered how to add authentication to our React application with Strapi.
To follow through with this article, you need:
We’ll build a simple social card application, where users can register, log in, and edit their social cards.
To get started, we will begin by setting up the backend with strapi.
mkdir react_strapi_auth
cd react_strapi_auth
mkdir backend
mkdir frontend
Our project structure will look similar to this:
Change the directory to the backend
and then run either of the commands to create the Strapi project in the current folder.
Note:
.
at the end is to tell the strapi-app script to create a project in the current folder.# Change directory cd backend # Create Strapi project yarn create strapi-app . #or npx create-strapi-app@latest .
Next, choose the installation type Quickstart. It uses the default database SQLite
and is recommended. Once the installation is complete, the Strapi admin dashboard should automatically open in your browser. Fill out the details to create an admin account.
The collection is a table containing entity information. Strapi, by default, provides us with a User
collection with minimal fields like username, email, password, etc. For this tutorial, we will be extending the User
Collection by adding our fields.
Navigate to the Content-Type Builder > Collection Types > User .
Now, we will add 7 more fields to this collection. Click on + ADD ANOTHER FIELD to add these fields.
1 Default value : Hello There 👋,
2 Required field : ✅
3 Maximum length : 120
1 RegExp pattern: [(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)
1 RegExp pattern: [(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)
1 Unique field: ✅
1 Unique field: ✅
1 Unique field: ✅
Click on Save; this will save the changes to the collection type and restart the server.
As Strapi is secure by default, we cannot access data from the API unless we enable permissions. To set permissions:
Go to Authenticated and enable the following actions for the User , Auth and Permissions under Users-permissions.
1- User ( Select All ✅ )
1- Permission ( Select All ✅ )
You will need to create a few users for our application. To create new users, navigate to Content Manager > User and click on Create new entry. Fill out the required details and click on Save to create the entry. The backend setup is done; now, move forward and set up the front-end of our application.
Change the directory to the frontend
and then run either of the commands to create the react project in the current folder.
Note:
.
at the end is to tell the create-react-app script to create a project in the current folder.# Change directory cd frontend # Create react project npx create-react-app . #OR yarn create react-app .
Remove these files:
Rename these files accordingly:
For this application, we will be using antd
for UI components, react-icons
for icons, and react-router-dom
for routing.
Let's install them in our project.
yarn add antd react-router-dom react-icons
Alright, we have our frontend set up is ready. Now, let's code our social card application.
Create a src/constant.js
file and add the below content to it.
1 # src/constant.js
2
3 export const AVATAR_API = "https://ui-avatars.com/api";
4 export const API = "http://localhost:1337/api";
5 export const AUTH_TOKEN = "authToken";
6 export const BEARER = "Bearer";
If the avatar URL is not available, then we will use the avatar API to get the avatar by username.
Create a src/helpers.js
file and add the below content to it. These are the helpers that we will be using for managing the jwt token for the Authenticated user.
1 # src/helpers.js
2
3 import { AUTH_TOKEN } from "./constant";
4
5 export const getToken = () => {
6 return localStorage.getItem(AUTH_TOKEN);
7 };
8
9 export const setToken = (token) => {
10 if (token) {
11 localStorage.setItem(AUTH_TOKEN, token);
12 }
13 };
14
15 export const removeToken = () => {
16 localStorage.removeItem(AUTH_TOKEN);
17 };
Create a React context to store logged-in user details. React Context is designed to store data which is global and shared between components. You can read more about the react context here.
Create a folder named context
and create a file AuthContext.js
.
mkdir context
touch AuthContext.js
After creating the AuthContext.js
, add the code below content to it. Here, you are doing two things: first, we are creating the AuthContext to hold 3 things, user, isLoading and setUser method, and second, creating a custom hook by using the useContext hook with AuthContext, so that you can directly use this custom hook to get the AuthContext data.
1 # context/AuthContext.js
2 import { createContext, useContext } from "react";
3
4 export const AuthContext = createContext({
5 user: undefined,
6 isLoading: false,
7 setUser: () => {},
8 });
9
10 export const useAuthContext = () => useContext(AuthContext);
The next step is to create a provider component for the application which will use the above AuthContext. AuthProvider will be the wrapper component which will provide the authenticated user details.
This component will take one prop which is "children" and return it with AuthContext provider values. it will fetch the authenticated user details if the jwt token is present.
1 # components/AuthProvider/AuthProvider.jsx
2
3 import React, { useState } from "react";
4 import { AuthContext } from "../../context/AuthContext";
5 import { message } from "antd";
6 import { API, BEARER } from "../../constant";
7 import { useEffect } from "react";
8 import { getToken } from "../../helpers";
9
10 const AuthProvider = ({ children }) => {
11 const [userData, setUserData] = useState();
12 const [isLoading, setIsLoading] = useState(false);
13
14 const authToken = getToken();
15
16 const fetchLoggedInUser = async (token) => {
17 setIsLoading(true);
18 try {
19 const response = await fetch(`${API}/users/me`, {
20 headers: { Authorization: `${BEARER} ${token}` },
21 });
22 const data = await response.json();
23
24 setUserData(data);
25 } catch (error) {
26 console.error(error);
27 message.error("Error While Getting Logged In User Details");
28 } finally {
29 setIsLoading(false);
30 }
31 };
32
33 const handleUser = (user) => {
34 setUserData(user);
35 };
36
37 useEffect(() => {
38 if (authToken) {
39 fetchLoggedInUser(authToken);
40 }
41 }, [authToken]);
42
43 return (
44 <AuthContext.Provider
45 value={{ user: userData, setUser: handleUser, isLoading }}
46 >
47 {children}
48 </AuthContext.Provider>
49 );
50 };
51
52 export default AuthProvider;
Now, replace the src/index.jsx
content with the below content.
Here, you're wrapping your App component with AuthProvider and Router Component to handle authenticated users and routing respectively.
1 # src/index.jsx
2 import React from "react";
3 import ReactDOM from "react-dom/client";
4 import "./index.css";
5 import App from "./App";
6 import AuthProvider from "./components/AuthProvider/AuthProvider";
7 import { BrowserRouter as Router } from "react-router-dom";
8
9 const root = ReactDOM.createRoot(document.getElementById("root"));
10 root.render(
11 <React.StrictMode>
12 <AuthProvider>
13 <Router>
14 <App />
15 </Router>
16 </AuthProvider>
17 </React.StrictMode>
18 );
The application provider has been set up now; let's create a header component for our application.
Note: All of the CSS is available in the
src/index.css
file here.
The AppHeader
component will render four links based on authenticated user data. If user data is present, it will render the user profile link and a logout link. Else, it'll render links for registering and login into the application.
1 # components/Appheader/Appheader.jsx
2 import { Button, Space } from "antd";
3 import React from "react";
4 import { CgWebsite } from "react-icons/cg";
5 import { useNavigate } from "react-router-dom";
6 import { useAuthContext } from "../../context/AuthContext";
7 import { removeToken } from "../../helpers";
8
9 const AppHeader = () => {
10 const { user } = useAuthContext();
11 const navigate = useNavigate();
12
13 const handleLogout = () => {
14 removeToken();
15 navigate("/signin", { replace: true });
16 };
17
18 return (
19 <Space className="header_space">
20 <Button className="header_space_brand" href="/" type="link">
21 <CgWebsite size={64} />
22 </Button>
23 <Space className="auth_buttons">
24 {user ? (
25 <>
26 <Button className="auth_button_login" href="/profile" type="link">
27 {user.username}
28 </Button>
29 <Button
30 className="auth_button_signUp"
31 type="primary"
32 onClick={handleLogout}
33 >
34 Logout
35 </Button>
36 </>
37 ) : (
38 <>
39 <Button className="auth_button_login" href="/signin" type="link">
40 Login
41 </Button>
42 <Button
43 className="auth_button_signUp"
44 href="/signup"
45 type="primary"
46 >
47 SignUp
48 </Button>
49 </>
50 )}
51 </Space>
52 </Space>
53 );
54 };
55
56 export default AppHeader;
This component will be responsible for fetching all the available profiles in our application and then showing social cards for all the profiles.
1 # SocialCards/SocialCards.jsx
2
3 import {
4 Button,
5 Card,
6 Col,
7 Image,
8 message,
9 Row,
10 Space,
11 Spin,
12 Typography,
13 } from "antd";
14 import React, { useEffect, useState } from "react";
15 import {
16 AiFillTwitterCircle,
17 AiFillLinkedin,
18 AiFillGithub,
19 } from "react-icons/ai";
20
21 import { CgWebsite } from "react-icons/cg";
22 import { SiGmail } from "react-icons/si";
23 import { API, AVATAR_API } from "../../constant";
24
25 const SocialCards = () => {
26 const [profiles, setProfiles] = useState([]);
27 const [isLoading, setIsLoading] = useState(false);
28
29 const fetchProfiles = async () => {
30 setIsLoading(true);
31 try {
32 const response = await fetch(`${API}/users`);
33 const data = await response.json();
34 setProfiles(data ?? []);
35 } catch (error) {
36 console.error(error);
37 message.error("Error while fetching profiles!");
38 } finally {
39 setIsLoading(false);
40 }
41 };
42
43 useEffect(() => {
44 fetchProfiles();
45 }, []);
46
47 if (isLoading) {
48 return <Spin size="large" />;
49 }
50
51 return (
52 <Row gutter={[32, 32]}>
53 {profiles.map((profile, index) => (
54 <Col md={8} lg={8} sm={24} xs={24} key={`${profile.id}_${index}`}>
55 <Card className="social_card">
56 <Space
57 className="social_card_space"
58 direction="vertical"
59 align="center"
60 >
61 <Image
62 className="social_image"
63 preview={false}
64 src={
65 profile.avatar_url ??
66 `${AVATAR_API}?name=${profile.username}&background=1890ff&color=fff`
67 }
68 />
69 <Typography.Title level={5}>{profile.username}</Typography.Title>
70 <Typography.Paragraph>{profile.about}</Typography.Paragraph>
71 <Space size={16} wrap>
72 {profile.twitter_username && (
73 <Button
74 className="social_button twitter"
75 href={`https://twitter.com/${profile.twitter_username}`}
76 type="link"
77 target="_blank"
78 >
79 <AiFillTwitterCircle size={24} />
80 </Button>
81 )}
82 {profile.linkedin_username && (
83 <Button
84 className="social_button linkedin"
85 href={`https://www.linkedin.com/in/${profile.linkedin_username}`}
86 type="link"
87 target="_blank"
88 >
89 <AiFillLinkedin size={24} />
90 </Button>
91 )}
92 {profile.github_username && (
93 <Button
94 className="social_button github"
95 href={`https://github.com/${profile.github_username}`}
96 type="link"
97 target="_blank"
98 >
99 <AiFillGithub size={24} />
100 </Button>
101 )}
102 {profile.website_url && (
103 <Button
104 className="social_button website"
105 href={profile.website_url}
106 type="link"
107 target="_blank"
108 >
109 <CgWebsite size={24} />
110 </Button>
111 )}
112 {profile.email && (
113 <Button
114 className="social_button gmail"
115 href={`mailto:${profile.email}`}
116 type="link"
117 target="_blank"
118 >
119 <SiGmail size={24} />
120 </Button>
121 )}
122 </Space>
123 </Space>
124 </Card>
125 </Col>
126 ))}
127 </Row>
128 );
129 };
130
131 export default SocialCards;
You will start by creating the signup page component and there, add a form which will contain 3 fields: username
, email
and password
. You'll use the custom hook, useAuthContext
, to get the setUser
method to set the Authenticated user data after a successful registration.
You'll also use useScreenSize
custom hook to get the user screen type. You can get the code for it hooks/useScreenSize.jsx. Once the user clicks on the submit button, it will call the onFinish
method which will call the /auth/local/register
endpoint with user data to create the user. On Success, you'll store the authenticated user data and jwt token and redirect users to their profile.
1 # pages/SignUp/SignUp.jsx
2
3 import {
4 Alert,
5 Button,
6 Card,
7 Col,
8 Form,
9 Input,
10 message,
11 Row,
12 Spin,
13 Typography,
14 } from "antd";
15 import React, { Fragment, useState } from "react";
16 import { Link } from "react-router-dom";
17 import { useNavigate } from "react-router-dom";
18 import { useAuthContext } from "../../context/AuthContext";
19 import useScreenSize from "../../hooks/useScreenSize";
20 import { API } from "../../constant";
21 import { setToken } from "../../helpers";
22
23 const SignUp = () => {
24 const { isDesktopView } = useScreenSize();
25 const navigate = useNavigate();
26
27 const { setUser } = useAuthContext();
28
29 const [isLoading, setIsLoading] = useState(false);
30
31 const [error, setError] = useState("");
32
33 const onFinish = async (values) => {
34 setIsLoading(true);
35 try {
36 const response = await fetch(`${API}/auth/local/register`, {
37 method: "POST",
38 headers: {
39 "Content-Type": "application/json",
40 },
41 body: JSON.stringify(values),
42 });
43
44 const data = await response.json();
45 if (data?.error) {
46 throw data?.error;
47 } else {
48 // set the token
49 setToken(data.jwt);
50
51 // set the user
52 setUser(data.user);
53
54 message.success(`Welcome to Social Cards ${data.user.username}!`);
55
56 navigate("/profile", { replace: true });
57 }
58 } catch (error) {
59 console.error(error);
60 setError(error?.message ?? "Something went wrong!");
61 } finally {
62 setIsLoading(false);
63 }
64 };
65
66 return (
67 <Fragment>
68 <Row align="middle">
69 <Col span={isDesktopView ? 8 : 24} offset={isDesktopView ? 8 : 0}>
70 <Card title="SignUp">
71 {error ? (
72 <Alert
73 className="alert_error"
74 message={error}
75 type="error"
76 closable
77 afterClose={() => setError("")}
78 />
79 ) : null}
80 <Form
81 name="basic"
82 layout="vertical"
83 onFinish={onFinish}
84 autoComplete="off"
85 >
86 <Form.Item
87 label="Username"
88 name="username"
89 rules={[
90 {
91 required: true,
92 type: "string",
93 },
94 ]}
95 >
96 <Input placeholder="Username" />
97 </Form.Item>
98 <Form.Item
99 label="Email"
100 name="email"
101 rules={[
102 {
103 required: true,
104 type: "email",
105 },
106 ]}
107 >
108 <Input placeholder="Email address" />
109 </Form.Item>
110
111 <Form.Item
112 label="Password"
113 name="password"
114 rules={[{ required: true }]}
115 >
116 <Input.Password placeholder="Password" />
117 </Form.Item>
118
119 <Form.Item>
120 <Button
121 type="primary"
122 htmlType="submit"
123 className="login_submit_btn"
124 >
125 Submit {isLoading && <Spin size="small" />}
126 </Button>
127 </Form.Item>
128 </Form>
129 <Typography.Paragraph className="form_help_text">
130 Already have an account? <Link to="/signin">Sign In</Link>
131 </Typography.Paragraph>
132 </Card>
133 </Col>
134 </Row>
135 </Fragment>
136 );
137 };
138
139 export default SignUp;
If the user already has an account, they'll need a login form to authenticate the application. You'll create a Sign-In page to handle that. Once the user clicks on the submit button, it will call the onFinish
method, which will call the /auth/local
endpoint with user data to create the user. On Success, it'll store the authenticated user data and jwt token and redirect users to their profile.
1 # pages/SignIn/SignIn.jsx
2
3 import {
4 Alert,
5 Button,
6 Card,
7 Col,
8 Form,
9 Input,
10 message,
11 Row,
12 Spin,
13 Typography,
14 } from "antd";
15 import React, { Fragment, useState } from "react";
16 import { Link } from "react-router-dom";
17 import { useNavigate } from "react-router-dom";
18 import { useAuthContext } from "../../context/AuthContext";
19 import useScreenSize from "../../hooks/useScreenSize";
20 import { API } from "../../constant";
21 import { setToken } from "../../helpers";
22
23 const SignIn = () => {
24 const { isDesktopView } = useScreenSize();
25 const navigate = useNavigate();
26
27 const { setUser } = useAuthContext();
28
29 const [isLoading, setIsLoading] = useState(false);
30
31 const [error, setError] = useState("");
32
33 const onFinish = async (values) => {
34 setIsLoading(true);
35 try {
36 const value = {
37 identifier: values.email,
38 password: values.password,
39 };
40 const response = await fetch(`${API}/auth/local`, {
41 method: "POST",
42 headers: {
43 "Content-Type": "application/json",
44 },
45 body: JSON.stringify(value),
46 });
47
48 const data = await response.json();
49 if (data?.error) {
50 throw data?.error;
51 } else {
52 // set the token
53 setToken(data.jwt);
54
55 // set the user
56 setUser(data.user);
57
58 message.success(`Welcome back ${data.user.username}!`);
59
60 navigate("/profile", { replace: true });
61 }
62 } catch (error) {
63 console.error(error);
64 setError(error?.message ?? "Something went wrong!");
65 } finally {
66 setIsLoading(false);
67 }
68 };
69
70 return (
71 <Fragment>
72 <Row align="middle">
73 <Col span={isDesktopView ? 8 : 24} offset={isDesktopView ? 8 : 0}>
74 <Card title="SignIn">
75 {error ? (
76 <Alert
77 className="alert_error"
78 message={error}
79 type="error"
80 closable
81 afterClose={() => setError("")}
82 />
83 ) : null}
84 <Form
85 name="basic"
86 layout="vertical"
87 onFinish={onFinish}
88 autoComplete="off"
89 >
90 <Form.Item
91 label="Email"
92 name="email"
93 rules={[
94 {
95 required: true,
96 type: "email",
97 },
98 ]}
99 >
100 <Input placeholder="Email address" />
101 </Form.Item>
102
103 <Form.Item
104 label="Password"
105 name="password"
106 rules={[{ required: true }]}
107 >
108 <Input.Password placeholder="Password" />
109 </Form.Item>
110
111 <Form.Item>
112 <Button
113 type="primary"
114 htmlType="submit"
115 className="login_submit_btn"
116 >
117 Login {isLoading && <Spin size="small" />}
118 </Button>
119 </Form.Item>
120 </Form>
121 <Typography.Paragraph className="form_help_text">
122 New to Social Cards? <Link to="/signup">Sign Up</Link>
123 </Typography.Paragraph>
124 </Card>
125 </Col>
126 </Row>
127 </Fragment>
128 );
129 };
130
131 export default SignIn;
The profile component will hold the authenticated user data and allow the user to update the data. Once the user is logged in to the application, it will redirect to the profile component and this route will be a protected route.
1 # components/Profile/Profile.jsx
2
3 import React from "react";
4 import { Button, Card, Col, Form, Input, message, Row, Spin } from "antd";
5 import { useAuthContext } from "../../context/AuthContext";
6 import { API } from "../../constant";
7 import { useState } from "react";
8 import { getToken } from "../../helpers";
9
10 const Profile = () => {
11 const [loading, setLoading] = useState(false);
12 const { user, isLoading, setUser } = useAuthContext();
13
14 const handleProfileUpdate = async (data) => {
15 setLoading(true);
16 try {
17 const response = await fetch(`${API}/users/${user.id}`, {
18 method: "PUT",
19 headers: {
20 "Content-Type": "application/json",
21 // set the auth token to the user's jwt
22 Authorization: `Bearer ${getToken()}`,
23 },
24 body: JSON.stringify(data),
25 });
26 const responseData = await response.json();
27
28 setUser(responseData);
29 message.success("Data saved successfully!");
30 } catch (error) {
31 console.error(Error);
32 message.error("Error While Updating the Profile!");
33 } finally {
34 setLoading(false);
35 }
36 };
37
38 if (isLoading) {
39 return <Spin size="large" />;
40 }
41
42 return (
43 <Card className="profile_page_card">
44 <Form
45 layout="vertical"
46 initialValues={{
47 username: user?.username,
48 email: user?.email,
49 twitter_username: user?.twitter_username,
50 linkedin_username: user?.linkedin_username,
51 github_username: user?.github_username,
52 avatar_url: user?.avatar_url,
53 website_url: user?.website_url,
54 about: user?.about,
55 }}
56 onFinish={handleProfileUpdate}
57 >
58 <Row gutter={[16, 16]}>
59 <Col md={8} lg={8} sm={24} xs={24}>
60 <Form.Item
61 label="Username"
62 name="username"
63 rules={[
64 {
65 required: true,
66 message: "Username is required!",
67 type: "string",
68 },
69 ]}
70 >
71 <Input placeholder="Username" />
72 </Form.Item>
73 </Col>
74 <Col md={8} lg={8} sm={24} xs={24}>
75 <Form.Item
76 label="Email"
77 name="email"
78 rules={[
79 {
80 required: true,
81 message: "Email is required!",
82 type: "email",
83 },
84 ]}
85 >
86 <Input placeholder="Email" />
87 </Form.Item>
88 </Col>
89 <Col md={8} lg={8} sm={24} xs={24}>
90 <Form.Item
91 label="Avatar Url"
92 name="avatar_url"
93 rules={[
94 {
95 type: "url",
96 },
97 ]}
98 >
99 <Input placeholder="Avatar Url" />
100 </Form.Item>
101 </Col>
102 <Col span={24}>
103 <Form.Item
104 label="About"
105 name="about"
106 rules={[
107 {
108 required: true,
109 type: "string",
110 max: 120,
111 },
112 ]}
113 >
114 <Input.TextArea placeholder="About" rows={6} />
115 </Form.Item>
116 </Col>
117 <Col md={8} lg={8} sm={24} xs={24}>
118 <Form.Item
119 label="Twitter Username"
120 name="twitter_username"
121 rules={[
122 {
123 type: "string",
124 },
125 ]}
126 >
127 <Input placeholder="Twitter Username" />
128 </Form.Item>
129 </Col>
130 <Col md={8} lg={8} sm={24} xs={24}>
131 <Form.Item
132 label="LinkedIn Username"
133 name="linkedin_username"
134 rules={[
135 {
136 type: "string",
137 },
138 ]}
139 >
140 <Input placeholder="LinkedIn Username" />
141 </Form.Item>
142 </Col>
143 <Col md={8} lg={8} sm={24} xs={24}>
144 <Form.Item
145 label="Github Username"
146 name="github_username"
147 rules={[
148 {
149 type: "string",
150 },
151 ]}
152 >
153 <Input placeholder="Github Username" />
154 </Form.Item>
155 </Col>
156 <Col md={8} lg={8} sm={24} xs={24}>
157 <Form.Item
158 label="Website Url"
159 name="website_url"
160 rules={[
161 {
162 type: "url",
163 },
164 ]}
165 >
166 <Input placeholder="Website Url" />
167 </Form.Item>
168 </Col>
169 </Row>
170 <Button
171 className="profile_save_btn"
172 htmlType="submit"
173 type="primary"
174 size="large"
175 >
176 {loading ? (
177 <>
178 <Spin size="small" /> Saving
179 </>
180 ) : (
181 "Save"
182 )}
183 </Button>
184 </Form>
185 </Card>
186 );
187 };
188
189 export default Profile;
There are four functionalities: register
, login
, logout
and Edit Profile
. The next step is to create routes for these and add them to the app component.
There'll be four routes: home
, signin
, signup
and profile
( protected ). For the profile route, there should be a condition that if the token is present, it should only render the profile component; otherwise it should redirect the user to the login page.
1 # src/Routes.jsx
2
3 import React from "react";
4 import { Routes, Route, Navigate } from "react-router-dom";
5 import Profile from "./components/Profile/Profile";
6 import SocialCards from "./components/SocialCards/SocialCards";
7 import { getToken } from "./helpers";
8 import SignIn from "./pages/SignIn/SignIn";
9 import SignUp from "./pages/SignUp/SignUp";
10
11 const AppRoutes = () => {
12 return (
13 <Routes>
14 <Route path="/" element={<SocialCards />} />
15 <Route path="/signin" element={<SignIn />} />
16 <Route path="/signup" element={<SignUp />} />
17 <Route
18 path="/profile"
19 element={getToken() ? <Profile /> : <Navigate to="/signin" />}
20 />
21 </Routes>
22 );
23 };
24
25 export default AppRoutes;
Finally, change the content of App.jsx
to the below content. It will render the AppRoutes component and everything will be taken care of by the AppRoutes.
1 # src/App.jsx
2
3 import React from "react";
4 import { Col, Layout, Row } from "antd";
5 import AppHeader from "./components/Appheader/Appheader";
6 import AppRoutes from "./Routes";
7 const { Header, Content } = Layout;
8
9 const App = () => {
10 return (
11 <Layout>
12 <Row gutter={[0, 32]}>
13 <Col span={24}>
14 <Header>
15 <AppHeader />
16 </Header>
17 </Col>
18 <Col span={22} offset={1}>
19 <Content>
20 <AppRoutes />
21 </Content>
22 </Col>
23 </Row>
24 </Layout>
25 );
26 };
27
28 export default App;
So far, we have seen how to build a React application with authentication using Strapi as a headless CMS. Let’s summarize what we’ve been able to achieve so far.
There are probably a few things you learned from this tutorial that you can apply to your projects. The Strapi and React application source code is available on GitHub and listed in the resources section if you need them.
Here are a few articles I think will be helpful:
Here is the code for the React frontend and Strapi backend:
Software Engineer | OpenSource Contributor | Technical Writer