Authentication with react.js – dev community

Action types

First we defined some string constant that indicates the type of action being performed.

actions/type.js

export const REGISTER_SUCCESS = "REGISTER_SUCCESS";
export const REGISTER_FAIL = "REGISTER_FAIL";
export const LOGIN_SUCCESS = "LOGIN_SUCCESS";
export const LOGIN_FAIL = "LOGIN_FAIL";
export const LOGOUT = "LOGOUT";
export const SET_MESSAGE = "SET_MESSAGE";
export const CLEAR_MESSAGE = "CLEAR_MESSAGE";

Add css style for react pages

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

Add react router

– Run the command: npm install react-router-dom.– Open src/index.js and wrap App component by BrowserRouter object.

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById("root")
);
serviceWorker.unregister();

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.

Похожее:  Законодательно закреплено понятие личного кабинета налогоплательщика | ФНС России | 50 Московская область

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.

Combine reducers

Because we only have a single store in a Redux application. We use reducer composition instead of many stores to split data handling logic.

reducers/index.js

import { combineReducers } from "redux";
import auth from "./auth";
import message from "./message";
export default combineReducers({
  auth,
  message,
});

Create react pages for authentication

In src folder, create new folder named components and add several files as following:

Create react router history

This is a custom history object used by the React Router.

helpers/history.js

import { createBrowserHistory } from "history";
export const history = createBrowserHistory();

Create redux actions

We’re gonna create two kind of actions in src/actions folder:

Create redux reducers

There will be two reducers in src/reducers folder, each reducer updates a different part of the state corresponding to dispatched Redux actions.

Create redux store

This Store will bring Actions and Reducers together and hold the Application state.

Create services

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

  • Authentication service
  • Data service

Creating a privateroute

Now we need to make the Panel accessible only after signing in. To do this we need to create a new component called PrivateRoute. We are creating src/components/PrivateRote.js:

Creating our components: panel and signin

First of all, in our src folder, we will create a new folder which is called screens. Here we will create Panel.js and SignIn.js. I will use bootstrap to style my components faster. If you want to do the same and you don’t know how to install bootstrap, please look here.

In src/screens/Panel.js:

In src/screens/SignIn.js:

Now we need to create our router. We will do it in App.js. For navigation in our app, we will be using react-router-dom. We need to install it with yarn or npm:

Now in src/App.js we will create routes for our app.

Fetch requests

And then we can get data protected by the token using authFetch. It has the same interface as fetch, so if you already use fetch in the code you can simply replace it by authFetch:

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 Node.js Express MongoDB– React Django

Serverless:– React Hooks Firebase Realtime Database: CRUD App– React Hooks Firestore example: CRUD app

Home page

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

I just wanna npm install … and go production

I already gathered the package that contains all described below (and a bit more). You just need to install it by the command:

npm install react-token-auth

And follow examples in the react-token-auth GitHub repository.

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;

Logout when token is expired

There are two ways to handle JWT Token expiration.For more details, please visit:Handle JWT Token expiration in React with Hooks

Message actions creator

This Redux action creator is for actions related to messages (notifications) from APIs.

actions/message.js

import { SET_MESSAGE, CLEAR_MESSAGE } from "./types";
export const setMessage = (message) => ({
  type: SET_MESSAGE,
  payload: message,
});
export const clearMessage = () => ({
  type: CLEAR_MESSAGE,
});

Message reducer

This reducer updates message state when message action is dispatched from anywhere in the application.

reducers/message.js

import { SET_MESSAGE, CLEAR_MESSAGE } from "../actions/types";
const initialState = {};
export default function (state = initialState, action) {
  const { type, payload } = action;
  switch (type) {
    case SET_MESSAGE:
      return { message: payload };
    case CLEAR_MESSAGE:
      return { message: "" };
    default:
      return state;
  }
}

Overview of react redux registration & login example

We will build a React.js application using Hooks in that:

Here are the screenshots:– Registration Page:

– Signup failed:

– Form Validation Support:

If you need Form Validation with React Hook Form 7, please visit:React Form Validation with Hooks example

– Login Page:

– Profile Page (for successful Login):

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

– Check Browser Local Storage:

– Check State in Redux using redux-devtools-extension:

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

Problem

Authorization is one of the first problems developers face upon starting a new project. And one of the most common types of authorization (from my experience) is the token-based authorization (usually using JWT).

From my perspective, this article looks like “what I wanted to read two weeks ago”. My goal was to write minimalistic and reusable code with a clean and straightforward interface. I had the next requirements for my implementation of the auth management:

For me one of the most challenging questions were:

But let’s solve the problems step by step. Firstly we will create a token provider to store tokens and provide possibility to listen to changes. After that, we will create an auth provider, actually wrapper around token provider to create hooks for React components, fetch on steroids and some additional methods. And in the end, we will look at how to use this solution in the project.

Project structure

This is folders & files structure for this React Redux Registration and Login application:

With the explanation in diagram above, you can understand the project structure easily.

But I need to say some things about Redux elements that we’re gonna use:– actions folder contains all the action creators (auth.js for register & login, message.js for response message from server).– reducers folder contains all the reducers, each reducer updates a different part of the application state corresponding to dispatched action.

If you want to use redux-toolkit instead, please visit:React Redux Login, Register example with redux-toolkit & Hooks

React component diagram with redux, router, axios

Let’s look at the diagram below.

– The App page is a container with React Router. It gets app state from Redux Store. Then the navbar now can display based on the state.

– Login & Register pages have form for data submission (with support of react-validation library). They dispatch auth actions (login/register) to Redux Thunk Middleware which uses auth.service to call API.

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).

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-redux-hooks-jwt-auth

Then add Router Dom Module for later use: npm install react-router-dom.

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-redux 7.2.1
  • redux 4.0.5
  • redux-thunk 2.3.0
  • react-router-dom 5
  • axios 0.19.2
  • react-validation 3.0.7
  • Bootstrap 4
  • validator 13.1.1

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.

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);
};

Добавление возможностей создания и редактирования публикацию

Подробно рассмотренному ранее редактированию пользователей, можно создать и функциональность добавления и редактирования публикаций. Для это применим знакомый <EditGuesser>, чтобы получить структуру компонента, а затем на её основе создадим пользовательский компонент и, при необходимости, внесем в него коррективы.

Conclusion

Congratulation!

Today we’ve done so many interesting things. I hope you understand the overall layers of our React Redux Registration & Login App using Hooks, LocalStorage, React Router, Thunk Middleware, Axios, Bootstrap. Now you can apply it in your project at ease.

Don’t forget to read this tutorial:Handle JWT Token expiration in React with Hooks

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

If you need Form Validation with React Hook Form 7, please visit:React Form Validation with Hooks example

Or use React Components:React Redux: Token Authentication example with JWT & Axios

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

Happy learning, see you again!

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

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