Tutorial: Build a React.js Application with User Login & Authentication | Stormpath

Add css style for react components

Open src/App.css and write some CSS code as following:

Auth provider

Let’s create a new class of objects that we will call as an Auth provider. The interface will contain 4 methods: hook useAuth() to get fresh status from React component, authFetch() to make requests to the network with the actual token and login(), logout() methods which will proxy calls to the method setToken() of the token provider (in this case, we will have only one entry point to the whole created functionality, and the rest of the code will not have to know about existing of the token provider). As before we will start from the function creator:

export const createAuthProvider = () => {

    /* Implementation */

    return {
        useAuth,
        authFetch,
        login,
        logout
    }
};

First of all, if we want to use a token provider we need to create an instance of it:

const tokenProvider = createTokenProvider();

Methods login() and logout() simply pass token to the token provider. I separated these methods only for explicit meaning (actually passing empty/null token removes data from local storage):

const login: typeof tokenProvider.setToken = (newTokens) => {
    tokenProvider.setToken(newTokens);
};

const logout = () => {
    tokenProvider.setToken(null);
};

The next step is the fetch function. According to my idea, this function should have exactly the same interface as original fetch and return the same format but should inject access token to each request.

Fetch function should take two arguments: request info (usually URL) and request init (an object with method, body. headers and so on); and returns promise for the response:

const authFetch = async (input: RequestInfo, init?: RequestInit): Promise<Response> => {
    const token = await tokenProvider.getToken();

    init = init || {};

    init.headers = {
        ...init.headers,
        Authorization: `Bearer ${token}`,
    };

    return fetch(input, init);
};

Inside the function we made two things: took a token from the token provider by statement await tokenProvider.getToken(); (getToken already contains the logic of updating the token after expiration) and injecting this token into Authorization header by the line Authorization: ‘Bearer ${token}’. After that, we simply return fetch with updated arguments.

So, we already can use the auth provider to save tokens and use them from fetch. The last problem is that we can not react to the token changes from our components. Time to solve it.

Configuring babel

Since we’ll be using ES6 and JSX, we need to transpile these files into ES5 (for backwards compatibility with non-modern browsers). This is where Babel comes in. Babel can take our ES6/JSX files as input, and convert those to ES5.

To use Babel, start by installing some dependencies:

Now we’ll instruct Babel on how to compile our files, so create a new file named .babelrc and add this code it:

Finally, in order to get Babel to work with Webpack, we need to edit webpack.config.js and add an entry to the module.loaders array, as shown below:

Configuring webpack

Before you get too excited, kill the server and install Webpack so that we can package all of our client-side scripts (we’ll need this organization soon).

Configure Webpack by creating a new file named webpack.config.js and put the code below in it:

What this will do is look in our /src/ directory (that we’ll create shortly) and package all of the scripts and their dependencies under that directory as one module. Then use the file /src/app.js and its exports as the export of that module.

But in order for Express to serve Webpack files, we have to open up server.js and add these lines to the top of it:

Then immediately after the line var app = express(); add:

As I mentioned before, this will allow Webpack to intercept requests and serve our packaged /js/app.js file.

Create services

We’re gonna create two services in src/services folder:

Deploying the react app to aws

This video shows how to setup a production ready web server from scratch on AWS, then deploy the example React Redux app and configure it to run with a real Node.js MongoDB backend api. The tutorial used in the video is available at React Node.js on AWS – How to Deploy a MERN Stack App to Amazon EC2.

Deploying the react app to microsoft azure

For instructions on how to deploy the React app to Azure with a real backend api built with ASP.NET Core and SQL Server see React ASP.NET Core on Azure with SQL Server – How to Deploy a Full Stack App to Microsoft Azure.

Further reading

Fullstack CRUD:– React Spring Boot MySQL– React Spring Boot PostgreSQL– React Spring Boot MongoDB– React Node.js Express MySQL– React Node.js Express PostgreSQL– React.js Node.js Express MongoDB– React Django

Another way to implement Form Validation:React Form Validation example with Formik and Yup

Home page

This is a public page that shows public content. People don’t need to log in to view this page.

Home route

Now when we’ve setup most of our routing. Let’s look at a special route called the HomeRoute. This route itself doesn’t do anything. But acts as a “marker”, to indicate where to redirect to when logging in and logging out.

So in order to specify where we want to end up when we log out, open up app.js and change the:

Into:

Now when logging out, the Stormpath SDK will know that it should redirect to the ‘/’ path. Now, to specify where to redirect when logging out, change the AuthenticatedRoute that we created in the previous step:

So that it looks like:

Notice how the AuthenticatedRoute wraps the HomeRoute. This is used to indicate the authenticated route that we want to redirect to after login.

Import bootstrap

Run command: npm install bootstrap.

Open src/App.js and modify the code inside it as following-

import React, { Component } from "react";
import "bootstrap/dist/css/bootstrap.min.css";
class App extends Component {
  render() {
    // ...
  }
}
export default App;

Importing components

To be able to reference our pages we need to import them. And in order to make importing easy, we’ll put them all together in an index.js file so we only have to import it once. So let’s create a new file named index.js in our pages directory and export all of our pages from it, as shown below:

With this, we’ll only have to do one import in order to have access to all of our pages.

So let’s do that. Open up app.js file and at the top of the file, add the following import statement:

Index page

In our MasterPage notice the property this.props.children. This will contain the components of the child routes that our router match. So if we had a route that looked like:

And we tried to access /hello. The this.props.children array would be populated with a HelloPage component and for that reason that component would be rendered in our master page.

Now imagine the scenario where you try to access /. Without any this.props.children, this would only render your master page but with empty content. This is where IndexRoute comes into play. With an IndexRoute you can specify the component that should be rendered when you hit the path of the master page route (in our case /).

But before we add our IndexRoute to our router, let’s create a new file in our pages directory named IndexPage.js and add the following to it:

Now let’s add our IndexRoute. Open up app.js and inside the tag <Route path=’/’ component={MasterPage}> add your IndexRoute so that it looks like the following:

Index.html and bootstrap

Now, before getting our hands dirty with React, we’ll prepare the entry page for our app. This page will tell the browser what it must load before we initialize React and our application. So create a new directory named build, then within that, put a file named index.html. Our server will serve all of our static files from this folder.

Then within index.html, put the following:

Also, under the build directory, create a new directory named css and download Bootstrap to it. Name the file bootstrap.min.css.

Now in order for our browser to be able to access these files we need to configure them so that they are served through Express. So open up server.js and at the top of the file add:

Then under the line app.use(stormpath.init(app, …)); add:

Jsx and reserved javascript identifiers

Since JSX is JavaScript, there are some caveats that you need to know when working with React. I.e. when setting properties of a React DOM component you cannot use neither for or class since those are considered reserved JavaScript identifiers.

To illustrate the issue, this won’t work:

But this will:

Jsx variables

Variables can easily be interpolated into your JSX DOM using { nameOfVariable }, e.g. as shown below:

Logout when the token is expired

There are two ways. For more details, please visit:React – How to Logout when JWT Token is expired

Main react entry file

Path: /src/index.jsx

The root index.jsx file bootstraps the react redux tutorial application by rendering the App component (wrapped in a redux Provider) into the app div element defined in the base index html file above.

The boilerplate application uses a fake / mock backend that stores data in browser local storage, to switch to a real backend api simply remove the fake backend code below the comment // setup fake backend.

import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';

import { store } from './_helpers';
import { App } from './App';

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

render(
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById('app')
);

Master page

Before we create our pages, we have to set up our router. The router is what determines how we’ll be able to navigate around in our React application. We’ll start by creating a shared root route. This will act as our “master page”. I.e. all routes under this route will all share the same master component (header). So place the code below inside the <Router> tag in app.js so that it looks like this:

As you can see, we have referenced MasterPage. Something that doesn’t exist yet. So let’s go ahead and create that in a new directory that we’ll name pages, in our src folder.

Now create a new file named MasterPage.js and add this code to it:

As you can see, we don’t have a Header component yet, so let’s go and create a new file named Header.js in the same directory with the following content:

Overview of react jwt authentication example

We will build a React application in that:

Here are the screenshots:– Signup Page:

– Form Validation Support:

– Login Page:

– Profile Page (for successful Login):

– For Moderator account login, the navigation bar will change by authorities:

– Check Browser Local Storage:

Похожее:  Росгосстрах: вход в личный кабинет и регистрация

If you want to add refresh token, please visit:React Refresh Token with JWT and Axios Interceptors

React redux helpers folder

Path: /src/_helpers

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.

React redux home page component

Path: /src/HomePage/HomePage.jsx

React redux login page component

Path: /src/LoginPage/LoginPage.jsx

React redux register page component

Path: /src/RegisterPage/RegisterPage.jsx

React redux services folder

Path: /src/_services

React auth header

Path: /src/_helpers/auth-header.js

React component diagram with router, axios & localstorage

Let’s look at the diagram below.

– The App component is a container with React Router (BrowserRouter). Basing on the state, the navbar can display its items.

– Login & Register components have form for data submission (with support of react-validation library). They call methods from auth.service to make login/register request.

React components folder

Path: /src/_components

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

React private route component

Path: /src/_components/PrivateRoute.jsx

React router history

Path: /src/_helpers/history.js

React tutorial package.json

Path: /package.json

The package.json file contains project configuration information including package dependencies which get installed when you run npm install. Full documentation is available on the npm docs website.

Redux actions folder

Path: /src/_actions

Redux alert action constants

Path: /src/_constants/alert.constants.js

The alert constants object contains the redux alert action types used to display and clear alerts in the react application.

export const alertConstants = {
    SUCCESS: 'ALERT_SUCCESS',
    ERROR: 'ALERT_ERROR',
    CLEAR: 'ALERT_CLEAR'
};

Redux alert action creators

Path: /src/_actions/alert.actions.js

Contains Redux action creators for actions related to alerts / toaster notifications in the application. For example to display a success alert message with the text ‘Registration Successful’ you can call dispatch(alertActions.success(‘Registration successful’));.

I’ve wrapped the action methods in an alertActions object at the top of the file so it’s easy to see all available actions at a glance and simplifies importing them into other files. The implementation details for each action creator are placed in the below functions.

import { alertConstants } from '../_constants';

export const alertActions = {
    success,
    error,
    clear
};

function success(message) {
    return { type: alertConstants.SUCCESS, message };
}

function error(message) {
    return { type: alertConstants.ERROR, message };
}

function clear() {
    return { type: alertConstants.CLEAR };
}

Redux alert reducer

Path: /src/_reducers/alert.reducer.js

The redux alert reducer manages the application state for alerts / toaster notifications, it updates state when an alert action is dispatched from anywhere in the application, for example when an alertConstants.SUCCESS action is dispatched, the reducer updates the alert state to an object with type: ‘alert-success’ and message: action.message.

import { alertConstants } from '../_constants';

export function alert(state = {}, action) {
  switch (action.type) {
    case alertConstants.SUCCESS:
      return {
        type: 'alert-success',
        message: action.message
      };
    case alertConstants.ERROR:
      return {
        type: 'alert-danger',
        message: action.message
      };
    case alertConstants.CLEAR:
      return {};
    default:
      return state
  }
}

Redux authentication reducer

Path: /src/_reducers/authentication.reducer.js

Redux reducers folder

Path: /src/_reducers

The _reducers folder contains all the Redux reducers for the project, each reducer updates a different part of the application state in response to dispatched redux actions.

Redux store

Path: /src/_helpers/store.js

The redux store helper calls createStore() to create the centralized redux state store for the entire react application.

import { createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import { createLogger } from 'redux-logger';
import rootReducer from '../_reducers';

const loggerMiddleware = createLogger();

export const store = createStore(
    rootReducer,
    applyMiddleware(
        thunkMiddleware,
        loggerMiddleware
    )
);

Register page

This page is similar to Login Page.

For Form Validation, there are some more details:

We’re gonna call AuthService.register() method and show response message (successful or error).

Registration page

Now, let’s add a page where people can sign up. We’ll call it RegistrationPage. So create a new file named RegistrationPage.js and put the following content in it:

Role-based pages

We’re gonna have 3 pages for accessing protected data:

Setup react.js project

Open cmd at the folder you want to save Project folder, run command:npx create-react-app react-jwt-auth

Solution

Before solving the problem I will make an assumption that we have a backend that returns an object with access and refresh tokens. Each token has a JWT format. Such an object may look like:

{
  "accessToken": "...",
  "refreshToken": "..."
}

Actually, the structure of the tokens object is not critical for us. In the simplest case, it might be a string with an infinite access token. But we want to look at how to manage a situation when we have two tokens, one of them may expire, and the second one might be used to update the first one.

Source code

You can find the complete source code for this example on Github.

Typescript version: React Typescript JWT Authentication (without Redux) example

Technology

We’re gonna use these modules:

  • React 16
  • react-router-dom 5.1.2
  • axios 0.19.2
  • react-validation 3.0.7
  • Bootstrap 4
  • validator 12.2.0

Token provider

As I mentioned before, our first step is creating the token provider. The token provider will work directly with local storage and all changes of token we will do through it. It will allow us to listen to changes from anywhere and immediately notify the listeners about changes (but about it a bit later). The interface of the provider will have the next methods:

Function createTokenProvider() will create an instance of the token provider with the described interface:

const createTokenProvider = () => {

    /* Implementation */

    return {
        getToken,
        isLoggedIn,
        setToken,
        subscribe,
        unsubscribe,
    };
};

All the next code should be inside the createTokenProvider function.

Let’s start by creating a variable for storing tokens and restoring the data from local storage (to be sure that the session will not be lost after page reload):

let _token: { accessToken: string, refreshToken: string } = 
    JSON.parse(localStorage.getItem('REACT_TOKEN_AUTH') || '') || null;

Now we need to create some additional functions to work with JWT tokens. At the current moment, the JWT token looks like a magic string, but it is not a big deal to parse it and try to extract the expiration date. The function getExpirationDate() will take a JWT token as a parameter and return expiration date timestamp on success (or null on failure):

const getExpirationDate = (jwtToken?: string): number | null => {
    if (!jwtToken) {
        return null;
    }

    const jwt = JSON.parse(atob(jwtToken.split('.')[1]));

    // multiply by 1000 to convert seconds into milliseconds
    return jwt && jwt.exp && jwt.exp * 1000 || null;
};

And one more util function isExpired() to check is the timestamp expired. This function returns true if the expiration timestamp presented and if it is less than Date.now().

const isExpired = (exp?: number) => {
    if (!exp) {
        return false;
    }

    return Date.now() > exp;
};

Time to create first function of the token provider interface. Function getToken() should return token and update it if it is necessary. This function should be async because it may make a network request to update token.

Похожее:  Creating a Login Form with OTP Verification through Email in PHP Tutorial | Free Source Code Projects and Tutorials

Using created earlier functions we can check is the access tokens expired or not (isExpired(getExpirationDate(_token.accessToken))). And in the first case to make a request for updating token. After that, we can save tokens (with the not implemented yet function setToken()). And finally, we can return access token:

const getToken = async () => {
    if (!_token) {
        return null;
    }

    if (isExpired(getExpirationDate(_token.accessToken))) {
        const updatedToken = await fetch('/update-token', {
            method: 'POST',
            body: _token.refreshToken
        })
            .then(r => r.json());

        setToken(updatedToken);
    }

    return _token && _token.accessToken;
};

Function isLoggedIn() will be simple: it will return true if _tokens is not null and will not check for access token expiration (in this case we will not know about expiration access token until we get fail on getting token, but usually it is sufficient, and let us keep function isLoggedIn synchronous):

const isLoggedIn = () => {
    return !!_token;
};

I think it is a good time to create functionality for managing observers. We will implement something similar to the Observer pattern, and first of all, will create an array to store all our observers. We will expect that each element in this array is the function we should call after each change of tokens:

let observers: Array<(isLogged: boolean) => void> = [];

Now we can create methods subscribe() and unsubscribe(). The first one will add new observer to the created a bit earlier array, second one will remove observer from the list.

const subscribe = (observer: (isLogged: boolean) => void) => {
    observers.push(observer);
};

const unsubscribe = (observer: (isLogged: boolean) => void) => {
    observers = observers.filter(_observer => _observer !== observer);
};

Usage

To start to use what we implemented above, we need to create an instance of the auth provider. It will give us access to the functions useAuth(), authFetch(), login(), logout() related to the same token in the local storage (in general, nothing prevents you to create different instances of auth provider for different tokens, but you will need to parametrize the key you use to store data in the local storage):

export const {useAuth, authFetch, login, logout} = createAuthProvider();

React fake / mock backend

Path: /src/_helpers/fake-backend.js

The fake backend is used for running the tutorial example without a server api (backend-less). It monkey patches the fetch() function to intercept certain api requests and mimic the behaviour of a real api by managing data in browser local storage. Any requests that aren’t intercepted get passed through to the real fetch() function.

Conclusion

Congratulation!

Today we’ve done so many interesting things. I hope you understand the overall layers of our React JWT Authentication App (without Redux) using LocalStorage, React Router, Axios, Bootstrap. Now you can apply it in your project at ease.

You should continue to check if Token is expired and logout:React – How to Logout when JWT Token is expired

Or add refresh token:React Refresh Token with JWT and Axios Interceptors

If you want to use React Hooks for this example, you can find the implementation at:React Hooks: JWT Authentication (without Redux) example

Or using Redux for state management:React Redux: JWT Authentication & Authorization example

Happy learning, see you again!

Installing react dependencies

Now when we are acquainted with React, we’ll kick things off by installing some React dependencies:

Before we start coding, we need a place to put our React files, so create a new directory named src, and then use that as your working directory.

Now, let’s start with the entry point of our app. This will be the place where we will set up our React application and its routing. So create a new file named app.js and enter this code:

So now we have a foundation for our application. Let’s go ahead and import the Stormpath SDK and some things we’ll need in it. At the top of your app.js file, add the import statement:

As you can see in app.js there’s now two conflicting Router imports. Since ReactStormpath.Router extends from ReactRouter.Router we won’t be needing that anymore. So go ahead and remove the Router import from react-router. Important: Leave the other ReactRouter imports, we’ll be needing those later.

Now, we’ll initialize the Stormpath SDK. Add the following line right above ReactDOM.render().

That was easy! We’re now ready to start building our pages.

1 Звезда2 Звезды3 Звезды4 Звезды5 Звезд (1 оценок, среднее: 4,00 из 5)
Загрузка...

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector