×
Community Blog How to Implement Authentication in ReactJS Using JWT

How to Implement Authentication in ReactJS Using JWT

This tutorial describes how you can implement authentication requirements for a web application in ReactJS with JSON Web Token.

By Bineli Manga, Alibaba Cloud Community Blog author.

ReactJS is the widely used frontend framework, and JSON Web Token, JWT for short, is the most used authentication protocol on the web. So, as you might have guessed, it is pretty important to know how you can get started to create an application using these two frameworks together and how you can implement authentication in ReactJS using JWT. JWT is a web protocol that gets its name because it is based on the exchange of JSON files, which happen also to be called tokens, to provide authentication and authorization features.

In this tutorial, I will be showing you how you can implement a system of authentication for a web application developed in Javascript using the React web framework by creating two pages, one of which is a login page and the other is a secured page that needs to be logged in to get further access. We will also be using Webpack to transpile React source code to Javascript source code, bundle the modules together and Webpack development server as the development web server.

Requirements

Before you can proceed with this tutorial, make sure you have the following things.

  • A computer with the React CLI installed
  • Have NodeJS installed and ready,
  • Have the Visual Studio Code IDE set up.

Environment Setup

Implement the following steps to set up the environment.

1. Download and install NodeJS and Node Package Manager (NPM). For this tutorial, node v10.15 and NPM v6.4.1 are used.

2. Initialize the project folder using the following command:

   $ npm init

3. Install the required npm packages by running npm install from the command line in the project root folder for the following packages:

  • react, react-dom, react-router-dom, formik, yup, history, rxjs
  • dev dependencies: @babel/core, @babel/preset-env, @babel/preset-react, webpack, webpack-cli, webpack-dev-server, html-webpack-plugin, path

4. Start the application by running npm start from the command line in the project root folder.

5. By this step, the browser should automatically open at http://localhost:8080 with the login page of the React JWT authentication app demo. Then, after this and after you have set up the project environment, you can start developing the application by the presentation of the project's structure.

Project Structure

All the source code of the project will be located in the src folder. Within this folder, create a folder per feature (App, HomePage, LoginPage), and another set of folders for common code that is shared among other parts of the app (_components, _helpers, _services). The non-features folders are prefixed with an underscore to group them together and make it easy to distinguish between features and non-features folders.

Add an index.js file in every folder to group the exported modules from a folder together to ensure that they are imported using the folder path instead of the full module path, and to enable importing multiple modules in a single import (e.g. import {userService, authenticationService} from '@/_services').

Configure a path alias @ in the webpack.config.js that maps to the /src directory. This allows imports to be relative to the /src folder by prefixing the import with @, removing the need to use long relative paths like import MyComponent from '../../../MyComponent'.

Create Configuration Files of the Project

Create the webpack.config.js File

To configure how ReactJS source code will be organized, create the file webpack.config.js in the root folder of the project, using the following content.

js
var HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
module.exports = {
    mode: 'development',
    resolve: {
        extensions: ['.js', '.jsx']
    },
    module: {
        rules: [
            {
                test: /\.jsx?$/,
                loader: 'babel-loader'
            }
        ]
    },
    resolve: {
        extensions: ['.js', '.jsx'],
        alias: {
            '@': path.resolve(__dirname, 'src/'),
        }
    },
    plugins: [new HtmlWebpackPlugin({
        template: './src/index.html'
    })],
    devServer: {
        historyApiFallback: true
    },
    externals: {
        // global app config object
        config: JSON.stringify({
            apiUrl: 'http://localhost:4000'
        })
    }
}

Webpack compiles and bundles all the project files so they're ready to be loaded into a browser. It does this with the help of loaders and plugins that are configured in the webpack.config.js file. For more info about Webpack check out the Webpack Documnetation.

The Webpack configuration file also defines a global config object for the application using the externals property. Use it to define different configuration variables for the development and production environments.

Create the .babelrc File

The babel configuration file defines the presets used by babel to transpile the React to Javascript EcmaScript version 6 (ES6) code. The babel transpiler is run by Webpack via the babel-loader module configured in the webpack.config.js file as shown below.

js
{
    "presets": [
        "@babel/preset-react",
        "@babel/preset-env"
    ]
}

Create Entry Files

Create the index.jsx File

The root index.jsx file bootstraps the react application by rendering the app component into the app div element defined in the base index HTML file above.

The boilerplate application uses a fake backend by default. To switch to a real backend API, simply remove the fake backend code below the comment // setup fake backend.

js
import React from 'react';
import { render } from 'react-dom';

import { App } from './App';

// setup fake backend
import { configureFakeBackend } from './_helpers';
configureFakeBackend();

render(
    <App />,
    document.getElementById('app')
);

Create the index.html File

The base index.html file contains the outer HTML for the whole react application. When the app is started with npm start, Webpack bundles up all of the react code into a single javascript file and injects it into the body of the page.

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>React - JWT Authentication in React App</title>

    <!-- bootstrap css -->
    <link href="//netdna.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" rel="stylesheet" />

    <style>
        a { cursor: pointer; }
    </style>
</head>
<body>
    <div id="app"></div>
</body>
</html>

Create the App Component

The app component is the root component for the react application, and it contains the outer HTML, routes and main navigation bar for the react app.

It subscribes to the currentUser observable in the authentication service so it may reactively show/hide the main navigation bar when the user logs in/out of the application. Don't worry about unsubscribing from the observable here because it's the root component of the application. The only time the component will be destroyed is when the application is closed which would destroy any subscriptions as well.

The app component contains a logout() method which is called from the logout link in the main navigation bar to log the users out and redirect them to the login page.

javascript
import React from 'react';
import { Router, Route, Link } from 'react-router-dom';

import { history } from '@/_helpers';
import { authenticationService } from '@/_services';
import { PrivateRoute } from '@/_components';
import { HomePage } from '@/HomePage';
import { LoginPage } from '@/LoginPage';

class App extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            currentUser: null
        };
    }

    componentDidMount() {
        authenticationService.currentUser.subscribe(x => this.setState({ currentUser: x }));
    }

    logout() {
        authenticationService.logout();
        history.push('/login');
    }

    render() {
        const { currentUser } = this.state;
        return (
            <Router history={history}>
                <div>
                    {currentUser &&
                        <nav className="navbar navbar-expand navbar-dark bg-dark">
                            <div className="navbar-nav">
                                <Link to="/" className="nav-item nav-link">Home</Link>
                                <a onClick={this.logout} className="nav-item nav-link">Logout</a>
                            </div>
                        </nav>
                    }
                    <div className="jumbotron">
                        <div className="container">
                            <div className="row">
                                <div className="col-md-6 offset-md-3">
                                    <PrivateRoute exact path="/" component={HomePage} />
                                    <Route path="/login" component={LoginPage} />
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </Router>
        );
    }
}

export { App };

Create the LoginPage Component

The login page component contains a login form with the username and password fields. It displays validation messages for invalid fields when the user attempts to submit the form or when a field is touched. If the form is valid, the component calls the authenticationService.login(username, password) method, and if login is successful, the user is redirected back to the original page they were trying to access.

For this tutorial, the login form is built using Formik, a higher order component that helps to manage form state, validation, error messages, and form submission. Validation is done with the Yup object schema validator which hooks into Formik via the handy validationSchema prop.

Create the HomePage Component

The home page component is displayed after signing in to the application, it contains a simple welcome message and a list of all users. The component gets the current user from the authentication service and then fetches all users from the API by calling the userService.getAll() method from the componentDidMount() react lifecycle hook.

javascript
import React from 'react';

import { userService, authenticationService } from '@/_services';

class HomePage extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            currentUser: authenticationService.currentUserValue,
            users: null
        };
    }

    componentDidMount() {
        userService.getAll().then(users => this.setState({ users }));
    }

    render() {
        const { currentUser, users } = this.state;
        return (
            <div>
                <h1>Hi {currentUser.firstName}!</h1>
                <p>You're logged in with React & JWT!!</p>
                <h3>Users from secure api end point:</h3>
                {users &&
                    <ul>
                        {users.map(user =>
                            <li key={user.id}>{user.firstName} {user.lastName}</li>
                        )}
                    </ul>
                }
            </div>
        );
    }
}

export { HomePage };

The _services Folder

The _services layer handles all HTTP communication with backend API's for the application, each service encapsulates the API calls for a content type (e.g. users) and exposes methods for performing various operations (e.g. CRUD operations). Services may also have methods that don't wrap HTTP calls, for React the authenticationService.logout() method just removes the currentUser object from localStorage and sets it to null in the application.

It is recommended to wrap the HTTP calls and implementation details in a services layer as it provides a clean separation of concerns and simplifies the react components that use the services.

The User Service

The user service contains just a couple of methods for retrieving user data from the API, it acts as the interface between the Angular application and the backend API. It included the user service to demonstrate accessing secure API endpoints with the HTTP authorization header set after logging in to the application, the auth header is set with a JWT token in the auth-header.js helper above. The secure endpoints in the react are fake/mock routes implemented in the fake-backend.js helper above.

javascript
import config from 'config';
import { authHeader, handleResponse } from '@/_helpers';

export const userService = {
    getAll
};

function getAll() {
    const requestOptions = { method: 'GET', headers: authHeader() };
    return fetch(`${config.apiUrl}/users`, requestOptions).then(handleResponse);
}

The Authentication Service

The authentication service is used to login and logout of the application. For logging in, it posts the user's credentials to the /users/authenticate route on the API. If authentication is successful the user details including the token are added to local storage, and the current user is set in the application by calling currentUserSubject.next(user).

The RxJS subjects and observables are used by the service to store the current user state and communicate between different components in the application. To learn more about using React with RxJS check out React + RxJS - Communicating Between Components with Observable & Subject.

The logged-in user details are stored in local storage so the user will stay logged in even if they refresh the browser and also between browser sessions until they explicitly logout. If you don't want the user to stay logged in between refreshes or sessions, easily change the behavior by storing user details somewhere less persistent such as session storage which would persist between refreshes but not browser sessions, or remove the calls to localStorage which would cause the user to be logged out if the browser is refreshed.

There are two properties exposed by the authentication service for accessing the currently logged in user. Use the currentUser observable when you want a component to reactively update when a user logs in or out, for react in the App.jsx component so it may show/hide the main navigation bar when the user logs in/out. Use the currentUserValue property when you just want to get the current value of the logged-in user but don't need to reactively update when it changes, for reaction in the PrivateRoute.jsx component which restricts access to routes by checking if the user is currently logged in.

javascript
import { BehaviorSubject } from 'rxjs';

import config from 'config';
import { handleResponse } from '@/_helpers';

const currentUserSubject = new BehaviorSubject(JSON.parse(localStorage.getItem('currentUser')));

export const authenticationService = {
    login,
    logout,
    currentUser: currentUserSubject.asObservable(),
    get currentUserValue () { return currentUserSubject.value }
};

function login(username, password) {
    const requestOptions = {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ username, password })
    };

    return fetch(`${config.apiUrl}/users/authenticate`, requestOptions)
        .then(handleResponse)
        .then(user => {
            // store user details and jwt token in local storage to keep user logged in between page refreshes
            localStorage.setItem('currentUser', JSON.stringify(user));
            currentUserSubject.next(user);

            return user;
        });
}

function logout() {
    // remove user from local storage to log user out
    localStorage.removeItem('currentUser');
    currentUserSubject.next(null);
}

The Other Folders

The _components Folder

The _components folder contains shared React components that are used anywhere in the application.

The Private Route Component: It renders a route component if the user is logged in, and redirects the user to the /login page when they aren't logged-in.

The _helpers Folder

The helpers folder contains all the bits and pieces that don't fit into other folders but don't justify having a folder of their own.

  • Auth Header: A helper function that returns an HTTP Authorization header containing the JWT auth token of the currently logged in user. If the user isn't logged in, an empty object is returned. The auth header is used to make authenticated HTTP requests to the server API using JWT authentication.
  • The Fake Backend: It enables the react to run without a backend (backend-less), it contains a hardcoded collection of users and provides fake implementations for the API endpoints "authenticate" and "get all users'', these would be handled by a real API and database in a production application. The "authenticate" endpoint is used for logging in to the application and is publicly accessible, the "get all users" endpoint is restricted to users that are logged in.
    The fake backend is implemented by monkey patching the fetch() function to intercept certain API requests and mimic the behaviour of a real API. Any requests that aren't intercepted get passed through to the real fetch() function.
  • The handleResponse: It function checks responses from the API to see if the request was unauthorised, forbidden or unsuccessful. If the response status is 401 Unauthorized or 403 Forbidden then the user is automatically logged out of the application, this handles cases when the user token is no longer valid for any reason. If the response contains an error then a rejected promise is returned that includes the error message, otherwise if the request was successful then the response data is returned as a JSON object.
  • The History: It's a custom history object used by the React Router. This article uses a custom history object instead of the one built into React Router to enable redirecting users from outside React components, for reacting in the logout method of the App component.

Conclusion

To summarize things, in this tutorial we have shown you how you can use Json Web Token (JWT) to build authentication systems for ReatcJS apps. Now, to go further, you may want to explore how you can implement the same authentication with React+Redux, which makes for an even better management system in React applications.

0 0 0
Share on

Alibaba Clouder

1,704 posts | 291 followers

You may also like

Comments