I worked on a project to create a tiny boilerplate with Create React App to implement the authentication flow with Strapi, a Node.js framework with an extensible admin panel and built-in features (authentication, upload, permissions, etc.)
In this tutorial, we'll implement the basic authentication flow using JSON Web Tokens that a Strapi API provides and learn how to use authentication providers (Facebook, GitHub, Google, etc.) with Strapi to authenticate your users.
$ npx create-strapi-app my-app --quickstart
$ npx create-react-app good-old-react-authentication-flow
Below is the way I organize my code:
1 /src
2 └─── containers // React components associated with a Route
3 | └─── App // The entry point of the application
4 | └─── AuthPage // Component handling all the auth views
5 | └─── ConnectPage // Handles the auth with a custom provider
6 | └─── HomePage // Can be accessed only if the user is logged in
7 | └─── NotFoundPage // 404 Component
8 | └─── PrivateRoute // HoC
9 |
10 └─── components // Dummy components
11 |
12 └─── utils
13 └─── auth
14 └─── request // Request helper using fetch
To implement the authentication views, we first need to create a Higher Order Component HoC that will check if a user can access a specific URL. To do so, we just need to follow the official documentation and modify the fakeAuth
example and use our auth.js
helper:
npm i react-router-dom
Next, use the following code:
1 import React from 'react';
2 import { Navigate, Outlet } from 'react-router-dom';
3
4 import auth from '../../utils/auth';
5
6 const PrivateRoute = () => {
7 return auth.getToken() ? <Outlet /> : <Navigate to="/auth/login" />;
8 }
9
10 export default PrivateRoute;
To create the routing, update App.js:
1 import React from 'react';
2 import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
3 // Components
4 import AuthPage from '../AuthPage';
5 import ConnectPage from '../ConnectPage';
6 import HomePage from '../HomePage';
7 import NotFoundPage from '../NotFoundPage';
8 import PrivateRoute from '../PrivateRoute';
9 // Design
10 import './styles.css';
11 const App = () => {
12 return (
13 <Router>
14 <div className="App">
15 <Routes>
16 {/* A user can't go to the HomePage if is not authenticated */}
17 <Route path="/auth/:authType/:id" element={<AuthPage/>} />
18 <Route path="/auth/:authType" element={<AuthPage/>} />
19 <Route exact path='/' element={<PrivateRoute/>}>
20 <Route exact path='/' element={<HomePage/>}/>
21 </Route>
22 <Route exact path="/connect/:provider" element={<ConnectPage/>} />
23 <Route path="*" element={<NotFoundPage/>} />
24 </Routes>
25 </div>
26 </Router>
27 );
28 }
29 export default App;
Now that all our routes are implemented, we need the create our views. The way we declared our routes allows us to have one component that is responsible for creating the correct form according to the location
.
First of all, let's create a forms.json
file that will handle the creation of the form on each auth view:
The structure of the JSON
will be like the following (you can see a customBootstrapClass
key that is needed in the Input
component):
1 {
2 "views": {
3 "login": [
4 {
5 "customBootstrapClass": "col-md-12",
6 "label": "Username",
7 "name": "identifier",
8 "type": "text",
9 "placeholder": "johndoe@gmail.com"
10 },
11 {
12 "customBootstrapClass": "col-md-12",
13 "label": "Password",
14 "name": "password",
15 "type": "password"
16 },
17 {
18 "customBootstrapClass": "col-md-6",
19 "label": "Remember me",
20 "name": "rememberMe",
21 "type": "checkbox"
22 }
23 ]
24 },
25 "data": {
26 "login": {
27 "identifier": "",
28 "password": "",
29 "rememberMe": false
30 }
31 }
32 }
To set the form when the user navigates from auth/login
to auth/register
, we need to use the useEffect Hook:
1 useEffect(() => {
2 setState({ value: generateForm(search, id, authType), errors: [], didCheckErrors: false });
3 }, [])
The generateForm
method is in charge of getting the data from the forms.json
file above.
To create the form, we just need to map over the data retrieve in the forms.json
file.
1 const handleChange = ({ target }) =>
2 setState({
3 value: { ...state.value, [target.name]: target.value },
4 });
5
6 function AuthPage() {
7 const inputs = get(form, ['views', authType], []);
8 return (
9 <div>
10 <form onSubmit={handleSubmit}>
11 {inputs.map((input, key) => (
12 <Input
13 autoFocus={key === 0}
14 key={input.name}
15 name={input.name}
16 onChange={handleChange}
17 type={input.type}
18 value={get(state.value, [input.name], '')}
19 />
20 ))}
21 <Button type="submit" />
22 </form>
23 </div>
24 );
25 }
Well, at this point, all the views needed for authenticating your users should be created! We just need to make the API call to access the app.
To make the API call, I have a request
helper which you can get from the demo app so we just need to use it in our handleSubmit
function:
1 const handleSubmit = e => {
2 e.preventDefault();
3 const body = state.value;
4 const requestURL = getRequestURL(authType);
5
6 // This line is required for the callback url to redirect your user to app
7 if (authType === 'forgot-password') {
8 set(body, 'url', 'http://localhost:3000/auth/reset-password');
9 }
10
11 request(requestURL, { method: 'POST', body: state.value })
12 .then(response => {
13 auth.setToken(response.jwt, body.rememberMe);
14 auth.setUserInfo(response.user, body.rememberMe);
15 alert("Redirecting user")
16 redirectUser();
17 })
18 .catch(err => {
19 // TODO handle errors for other views
20 // This is just an example
21 const errors = [
22 { name: 'identifier', errors: [err.response.payload.error.message] },
23 ];
24 setState({ ...state.value, didCheckErrors: !state.didCheckErrors, errors });
25 });
26 };
27 const redirectUser = () => {
28 navigate(`/`);
29 };
Once we get the response from the API, we just store the needed information in either the localStorage
or the sessionStorage
and we redirect the user to the HomePage.
We just achieved the most difficult part because using a custom provider like Facebook is easy as pie!
Whatever you choose Facebook, GitHub or even Google, using a provider for authenticating your users with Strapi is easy. In this example, I will show you how to use it with Facebook.
Here is the flow:
At this point, we need to implement only one hook useEffect
, which makes the API call and redirects the user depending on the response in the ConnectPage
container:
1 const redirectUser = path => {
2 <Navigate to={path} />;
3 };
4
5 useEffect(() => {
6 const requestURL = `http://localhost:1337/api/auth/${provider}/callback${search}`;
7
8 request(requestURL, { method: 'GET' })
9 .then(response => {
10 auth.setToken(response.jwt, true);
11 auth.setUserInfo(response.user, true);
12 redirectUser('/');
13 })
14 .catch(err => {
15 console.log(err.response.payload);
16 redirectUser('/auth/login');
17 });
18 })
To do so, we need a SocialLink
component like the following:
1 /**
2 *
3 * SocialLink
4 *
5 */
6 import React from 'react';
7 import { capitalize } from 'lodash';
8 import PropTypes from 'prop-types';
9 import Button from '../../components/Button';
10 import './styles.css';
11
12 function SocialLink({ provider }) {
13 return (
14 <a href={`http://localhost:1337/api/connect/${provider}`} className="link">
15 <Button type="button" social={provider} style={{ width: '100%' }}>
16 <i className={`fab fa-${provider}`} style={{ marginRight: '10px' }} />
17 {capitalize(provider)}
18 </Button>
19 </a>
20 );
21 }
22
23 SocialLink.propTypes = {
24 provider: PropTypes.string.isRequired,
25 };
26 export default SocialLink;
And we need to add it to the AuthPage
:
1 function AuthPage() {
2 const providers = ['facebook', 'github', 'google', 'twitter']; // To remove a provider from the list just delete it from this array...
3
4 return (
5 <div>
6 {providers.map(provider => <SocialLink provider={provider} key={provider} />)}
7 {/* Some other code */}
8 </div>
9 );
10 }
That's pretty much what we need to do for the frontend application. Now, we just need to setup Strapi to enable custom providers.
To set up Facebook so we can register our users:
Go to Facebook developers and create an app called test
.
Consumer
and click Next.test
, and click the Create app
button.
This would take us to the Add Products page, where we would set up Facebook Login. http://localhost:3000
as your website URL.Facebook login
>Settings
, add: http://localhost:1337/api/connect/facebook/callback
in the Valid OAuth Redirect URLs
field.Now that you have created your app on Facebook, you need to configure the Facebook provider in your project.
Go to Settings > Providers tab of the USERS & PERMISSIONS PLUGIN section and fill the form like the following:
Don't forget to save your modifications.
Also, here’s an example which also has the authentication flow already implemented.
Feel free to share it and give your feedback in the comments!
A full-stack developer, technical writer, and lover of Jamstack, passionate about lifelong learning, open and inclusive communities, and a big advocate of open source.