Note: the source code of this article is available on GitHub.

Creating the project

Before all, you need to create a Strapi API:

$ npm install strapi@alpha -g

$ strapi new my-app

$ cd my-app && strapi start

And also, your front-end application:

$ npm install create-react-app -g

$ create-react-app good-old-react-authentication-flow

You need to register your first user and then you’re ready to go!

Front-end App Architecture

I’m a huge fan of the React Boilerplate architecture so I created something similar to organize my code:

/src

└─── containers // React components associated with a Route

| └─── App // The entry point of the application

| └─── AuthPage // Component handling all the auth views

| └─── ConnectPage // Handles the auth with a custom provider

| └─── HomePage // Can be accessed only if the user is logged in

| └─── NotFoundPage // 404 Component

| └─── PrivateRoute // HoC

|

└─── components // Dummy components

|

└─── utils

└─── auth

└─── request // Request helper using fetch

Router Setup and PrivateRoute

To implement the authentication views, we first need to create a HoC: Higher Order Component 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:

import React from 'react';

import { Redirect, Route } from 'react-router-dom';



// Utils

import auth from '../../utils/auth';



const PrivateRoute = ({ component: Component, ...rest }) => (

<Route {...rest} render={props => (

auth.getToken() !== null ? (

<Component {...props} />

) : (

<Redirect to={{

pathname: 'auth/login',

state: { from: props.location }

}}

/>

):

)} />

);

Let’s create the routing:

import React, { Component } from 'react';

import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';



// Components

import AuthPage from '../../containers/AuthPage';

import ConnectPage from '../../containers/ConnectPage';

import HomePage from '../../containers/HomePage';

import NotFoundPage from '../../containers/NotFoundPage';



// This component ios HoC that prevents the user from accessing a route if he's not logged in

import PrivateRoute from '../../containers/PrivateRoute';



// Design

import './styles.css';



class App extends Component {

render() {

return (

<Router>

<div className="App">

<Switch>

{/* A user can't go to the HomePage if is not authenticated */}

<PrivateRoute path="/" component={HomePage} exact />

<Route path="/auth/:authType/:id?" component={AuthPage} />

<Route exact path="/connect/:provider" component={ConnectPage} />

<Route path="" component={NotFoundPage} />

</Switch>

</div>

</Router>

);

}

}



export default App;

Creating the Authentication Views

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:

forgot-password

login

register

reset-password

This structure of the JSON will be like the following (you can see a customBootstrapClass key that is needed in the Input component):

{

"views": {

"login": [

{

"customBootstrapClass": "col-md-12",

"label": "Username",

"name": "identifier",

"type": "text",

"placeholder": "johndoe@gmail.com"

},

{

"customBootstrapClass": "col-md-12",

"label": "Password",

"name": "password",

"type": "password"

},

{

"customBootstrapClass": "col-md-6",

"label": "Remember me",

"name": "rememberMe",

"type": "checkbox"

}

]

},

"data": {

"login": {

"identifier": "",

"password": "",

"rememberMe": false

}

}

}

Setting the state on location change

To set the form when the user navigates from auth/login to auth/register we need to use the following lifecycles:

componentDidMount() {

// Generate the form with a function to avoid code duplication

// in other lifecycles

this.generateForm(this.props);

} componentWillReceiveProps(nextProps) {

// Since we use the same container for all the auth views we need to update

// the UI on location change

if (nextProps.location.match.params.authType !== this.props.location.match.params.authType) {

this.generateForm(nextProps);

}

}

The generateForm method is in charge of getting the data from the forms.json file above.

Creating the view

To create the form we just need to map over the data retrieve in the forms.json file.