Skip to main content

BezKoder

  • Courses
  • Full Stack
  • Spring
  • Node
  • Vue
  • Angular
  • React
  • Firebase
  • Django
  • Dart
  • JsonFormatter
  • Categories

React Redux Toolkit Authentication & Authorization example

Last modified: October 14, 2022 bezkoder React, Security

In this tutorial, we’re gonna build a React Redux Toolkit Authentication example (also Authorization) with Hooks, React Router, Axios, JWT, HttpOnly Cookie and Bootstrap. I will show you:

  • JWT Authentication Flow for User Login, Register, Logout
  • Storing JWT in HttpOnly Cookie and User Info in Local Storage
  • Project Structure for React, Redux Toolkit, JWT Authentication, Router, Axios
  • Working with Redux Actions, Reducers, Store using redux-toolkit
  • Creating React Function Components with Hooks & Form Validation
  • React Function Components for accessing protected Resources (Authorization)
  • Dynamic Navigation Bar in React App

Related Posts:
– In-depth Introduction to JWT-JSON Web Token
– React Hooks CRUD example with Axios and Web API
– React Hooks File Upload example with Axios & Progress Bar
– React Form Validation with Hooks example
– React Custom Hook

Fullstack (JWT Authentication & Authorization example):
– React + Spring Boot
– React + Node.js Express

The example without using Redux:
React JWT Auth example

Overview of React Redux Toolkit Auth example

We will build a React.js application using Hooks and redux-toolkit in that:

  • There are Login/Logout, Signup pages.
  • Form data will be validated by front-end before being sent to back-end.
  • Depending on User’s roles (admin, moderator, user), Navigation Bar changes its items automatically.

Here are the screenshots:
– Signup Page:

redux-toolkit-authentication-example-signup

– Form Validation support:

redux-toolkit-authentication-example-form-validation

More detail about Form Validation:
– React Form Validation example with React Hook Form
– React Form Validation example with Formik and Yup

– Login Page:

redux-toolkit-authentication-example-login

– Profile Page (for successful Login):

redux-toolkit-authentication-example-profile-page

– For Authorized account login (Moderator for example), the navigation bar will change:

redux-toolkit-authentication-example-authorization

– HttpOnly Cookie sent with HTTP Request:

redux-toolkit-authentication-example-httponly-cookie

– Check Browser Local Storage:

redux-toolkit-authentication-example-local-storage

– Check State in Redux:

redux-toolkit-authentication-example-redux-state

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

JWT Authentication & Authorization Flow

For JWT Authentication, we’re gonna call 3 endpoints:

  • POST api/auth/signup for User Registration
  • POST api/auth/signin for User Login
  • POST api/auth/signout for User Logout

The following flow shows you an overview of Requests and Responses that React Client will make or receive. This React Client uses JWT in HttpOnly Cookies while sending request to protected resources.

redux-toolkit-authentication-example-flow

You can find step by step to implement these back-end servers in following tutorial:

  • Spring Boot Login and Registration example with H2
  • Spring Boot Login and Registration example with MySQL
  • Spring Boot Login and Registration example with MongoDB
  • Node.js Express Login and Registration example with MySQL
  • Node.js Express Login and Registration example with MongoDB

React Component Diagram with Redux, Router, Axios

Let’s look at the diagram below.

redux-toolkit-authentication-example-project-overview

– 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 formik and yup library). They dispatch auth actions (login/register) to Redux Thunk Middleware which uses auth.service to call API.

– auth.service methods use axios to make HTTP requests. It also stores or get Users Info from Browser Local Storage inside these methods.

– Home page is public for all visitor.

– Profile page displays user information after the login action is successful.

– BoardUser, BoardModerator, BoardAdmin pages will be displayed in navbar by state user.roles. In these pages, we use user.service to access protected resources from Web API.

– user.service calls Rest API without caring about JWT because it is stored in HttpOnly Cookies.

Technology

We’re gonna use these modules:

  • React 18/17
  • react-redux 8
  • redux-toolkit 1.8.6
  • react-router-dom 6
  • axios 0.27.2
  • Bootstrap 4
  • formik 2.2.9
  • yup 0.32.11
  • http-proxy-middleware 2

Project Structure

This is folders & files structure for this React Redux Authentication application:

redux-toolkit-authentication-example-project-structure

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

I need to say some things about Redux elements that we’re gonna use.
– store.js is where we create the Redux store instance with auth and message reducers. Each reducer updates a different part of the application state corresponding to dispatched action.
– Reducer and actions for a single feature are defined together in each file of slices folder.

Additionally, EventBus is for emitting Logout event when the Token is expired.

Setup React.js Project

Open cmd at the folder you want to save Project folder, run command:
yarn create react-app redux-toolkit-auth --template redux
Or: npx create-react-app redux-toolkit-auth --template redux

After the process is finished, @reduxjs/toolkit and react-redux packages will be installed automatically.
With redux-toolkit, we don’t need to install redux-devtools-extension.

You need to delete auto-generated features folder.

Then add Router Dom Module for later use with command:
– yarn add react-router-dom
Or: npm install react-router-dom

Install react-redux and redux-toolkit

If you’ve already have an existing React project and you want to install Redux, run the following command:
yarn add react-redux @reduxjs/toolkit
Or npm install react-redux @reduxjs/toolkit

Import Bootstrap

Run command: yarn add [email protected]
Or npm install [email protected].

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

import React from "react";
import "bootstrap/dist/css/bootstrap.min.css";

const App = () => {
  // ...
}

export default App;

Create Services

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

  • Authentication service
  • Data service

services

auth.service.js (Authentication service)

user.service.js (Data service)


Before working with these services, we need to install Axios with command:
yarn add axios or npm install axios

Authentication service

The service uses Axios for HTTP requests and Local Storage for user information.
It provides following important functions:

  • login(): POST {username, password} & save User Profile to Local Storage
  • logout(): POST logout request, remove User Profile from Local Storage
  • register(): POST {username, email, password}
  • getCurrentUser(): get stored user information

services/auth.service.js

import axios from "axios";

const API_URL = "http://localhost:3000/api/auth/";

const register = (username, email, password) => {
  return axios.post(API_URL + "signup", {
    username,
    email,
    password,
  });
};

const login = (username, password) => {
  return axios
    .post(API_URL + "signin", {
      username,
      password,
    })
    .then((response) => {
      if (response.data.username) {
        localStorage.setItem("user", JSON.stringify(response.data));
      }

      return response.data;
    });
};

const logout = () => {
  localStorage.removeItem("user");
  return axios.post(API_URL + "signout").then((response) => {
    return response.data;
  });
};

const getCurrentUser = () => {
  return JSON.parse(localStorage.getItem("user"));
};

const AuthService = {
  register,
  login,
  logout,
  getCurrentUser,
}

export default AuthService;

Data service

We also have methods for retrieving data from server, in the case we access protected resources. Because HttpOnly Cookies will be automatically sent along with HTTP requests, so we just simply use Axios without caring JWT.

services/user.service.js

import axios from "axios";

const API_URL = "http://localhost:3000/api/test/";

const getPublicContent = () => {
  return axios.get(API_URL + "all");
};

const getUserBoard = () => {
  return axios.get(API_URL + "user");
};

const getModeratorBoard = () => {
  return axios.get(API_URL + "mod");
};

const getAdminBoard = () => {
  return axios.get(API_URL + "admin");
};

const UserService = {
  getPublicContent,
  getUserBoard,
  getModeratorBoard,
  getAdminBoard,
}

export default UserService;

Create Slice Reducers and Actions

Instead of creating many folders and files for Redux (actions, reducers, types,…), with redux-toolkit we just need all-in-one: slice.

A slice is a collection of Redux reducer logic and actions for a single feature.

For creating a slice, we need:

  • name to identify the slice
  • initial state value
  • one or more reducer functions to define how the state can be updated

Once a slice is created, we can export the generated Redux action creators and the reducer function for the whole slice.

Redux Toolkit provides createSlice() function that will auto-generate the action types and action creators for you, based on the names of the reducer functions you provide.

import { createSlice } from '@reduxjs/toolkit'

export const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    // add your non-async reducers here
    increment: (state) => {
      state.value += 1
    },
    decrement: (state) => {
      state.value -= 1
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload
    },
  },
  extraReducers: {
    // add your async reducers here
  }
})

// Action creators
export const { increment, decrement, incrementByAmount } = counterSlice.actions;

export default counterSlice.reducer;

In the code above, you can see that we write “mutating” logic in reducers. It doesn’t actually mutate the state because it uses the Immer library behind the scenes, which detects changes to a “draft state” and produces a brand new immutable state based off those changes.

We’re gonna create two Slices in src/app/slices folder:


slices

auth.js (register/login/logout actions)

message.js (set/clear message actions)


Message Slice

This updates message state when message action is dispatched from anywhere in the application. It exports 2 action creators:

  • setMessage
  • clearMessage

slices/message.js

import { createSlice } from "@reduxjs/toolkit";

const initialState = {};

const messageSlice = createSlice({
  name: "message",
  initialState,
  reducers: {
    setMessage: (state, action) => {
      return { message: action.payload };
    },
    clearMessage: () => {
      return { message: "" };
    },
  },
});

const { reducer, actions } = messageSlice;

export const { setMessage, clearMessage } = actions
export default reducer;

Authentication Slice

We’re gonna import AuthService to make asynchronous HTTP requests with trigger one or more dispatch in the result.

– register()

  • calls the AuthService.register(username, email, password)
  • dispatch setMessage if successful/failed

– login()

  • calls the AuthService.login(username, password)
  • dispatch setMessage if successful/failed

– logout(): calls the AuthService.logout().

setMessage is imported from message slice that we’ve created above.

We also need to use Redux Toolkit createAsyncThunk which provides a thunk that will take care of the action types and dispatching the right actions based on the returned promise. There are 3 async Thunks to be exported:

  • register
  • login
  • logout

slices/auth.js

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import { setMessage } from "./message";

import AuthService from "../services/auth.service";

const user = JSON.parse(localStorage.getItem("user"));

export const register = createAsyncThunk(
  "auth/register",
  async ({ username, email, password }, thunkAPI) => {
    try {
      const response = await AuthService.register(username, email, password);
      thunkAPI.dispatch(setMessage(response.data.message));
      return response.data;
    } catch (error) {
      const message =
        (error.response &&
          error.response.data &&
          error.response.data.message) ||
        error.message ||
        error.toString();
      thunkAPI.dispatch(setMessage(message));
      return thunkAPI.rejectWithValue();
    }
  }
);

export const login = createAsyncThunk(
  "auth/login",
  async ({ username, password }, thunkAPI) => {
    try {
      const data = await AuthService.login(username, password);
      return { user: data };
    } catch (error) {
      const message =
        (error.response &&
          error.response.data &&
          error.response.data.message) ||
        error.message ||
        error.toString();
      thunkAPI.dispatch(setMessage(message));
      return thunkAPI.rejectWithValue();
    }
  }
);

export const logout = createAsyncThunk("auth/logout", async () => {
  await AuthService.logout();
});

const initialState = user
  ? { isLoggedIn: true, user }
  : { isLoggedIn: false, user: null };

const authSlice = createSlice({
  name: "auth",
  initialState,
  extraReducers: {
    [register.fulfilled]: (state, action) => {
      state.isLoggedIn = false;
    },
    [register.rejected]: (state, action) => {
      state.isLoggedIn = false;
    },
    [login.fulfilled]: (state, action) => {
      state.isLoggedIn = true;
      state.user = action.payload.user;
    },
    [login.rejected]: (state, action) => {
      state.isLoggedIn = false;
      state.user = null;
    },
    [logout.fulfilled]: (state, action) => {
      state.isLoggedIn = false;
      state.user = null;
    },
  },
});

const { reducer } = authSlice;
export default reducer;

Create Redux Store

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

The Redux Toolkit configureStore() function automatically:

  • enable the Redux DevTools Extension.
  • sets up the thunk middleware by default, so you can immediately write thunks without more configuration.

In the previous part, we exported 2 reducers. Let’s import it, and pass it to configureStore():

Open app/store.js and modify it with following code:

import { configureStore } from '@reduxjs/toolkit'
import authReducer from "./slices/auth";
import messageReducer from "./slices/message";

const reducer = {
  auth: authReducer,
  message: messageReducer
}

export const store = configureStore({
  reducer: reducer,
  devTools: true,
});

Make Redux Store available

We will wrap our application with a <Provider> component. It makes the Redux store available to any nested components.

src/index.js

import React from 'react';
import { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux';
import { store } from './app/store';
import App from './App';
...

const container = document.getElementById('root');
const root = createRoot(container);

root.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);

Create React Pages for Authentication

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


components

Login.js

Register.js

Profile.js


Form Validation overview

Now we need a library for Form validation, so we’re gonna add formik and yup library to our project.

Run the command: yarn add formik yup
Or: npm install formik yup

Import following items:

import { Formik, Field, Form, ErrorMessage } from "formik";
import * as Yup from "yup";

This is how we use it with 3 important attributes:

  • initialValues
  • validationSchema
  • onSubmit
const Login = (props) => {
  ...

  const initialValues = {
    username: "",
    password: "",
  };

  const validationSchema = Yup.object().shape({
    username: Yup.string().required("This field is required!"),
    password: Yup.string().required("This field is required!"),
  });

  const handleLogin = (formValue) => {
    const { username, password } = formValue;
    ...
  }

  return (
    <Formik
      initialValues={initialValues}
      validationSchema={validationSchema}
      onSubmit={handleLogin}
    >
      <Form>
        <div>
          <label htmlFor="username">Password</label>
          <Field name="username" type="text" />
          <ErrorMessage name="username" component="div" />
        </div>

        <div>
          <label htmlFor="password">Password</label>
          <Field name="password" type="password" />
          <ErrorMessage name="password" component="div" />
        </div>

        <div>
          <button type="submit" disabled={loading}>
            Login
          </button>
        </div>
      </Form>
    </Formik>
  );
}

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

Login Page

This page has a Form with username & password.
– We’re gonna verify them as required field.
– If the verification is ok, we dispatch login action, then direct user to Profile page: navigate("/profile");, or show message with response error.

For getting the application state and dispatching actions, we use React Redux Hooks useSelector and useDispatch.
– by checking isLoggedIn, we can redirect user to Profile page.
– message gives us response message.

components/Login.js

import React, { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Navigate, useNavigate } from "react-router-dom";
import { Formik, Field, Form, ErrorMessage } from "formik";
import * as Yup from "yup";

import { login } from "../slices/auth";
import { clearMessage } from "../slices/message";

const Login = () => {
  let navigate = useNavigate();

  const [loading, setLoading] = useState(false);

  const { isLoggedIn } = useSelector((state) => state.auth);
  const { message } = useSelector((state) => state.message);

  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(clearMessage());
  }, [dispatch]);

  const initialValues = {
    username: "",
    password: "",
  };

  const validationSchema = Yup.object().shape({
    username: Yup.string().required("This field is required!"),
    password: Yup.string().required("This field is required!"),
  });

  const handleLogin = (formValue) => {
    const { username, password } = formValue;
    setLoading(true);

    dispatch(login({ username, password }))
      .unwrap()
      .then(() => {
        navigate("/profile");
        window.location.reload();
      })
      .catch(() => {
        setLoading(false);
      });
  };

  if (isLoggedIn) {
    return <Navigate to="/profile" />;
  }

  return (
    <div className="col-md-12 login-form">
      <div className="card card-container">
        <img
          src="//ssl.gstatic.com/accounts/ui/avatar_2x.png"
          alt="profile-img"
          className="profile-img-card"
        />
        <Formik
          initialValues={initialValues}
          validationSchema={validationSchema}
          onSubmit={handleLogin}
        >
          {({ errors, touched }) => (
            <Form>
              <div className="form-group">
                <label htmlFor="username">Username</label>
                <Field
                  name="username"
                  type="text"
                  className={
                    "form-control" +
                    (errors.username && touched.username ? " is-invalid" : "")
                  }
                />
                <ErrorMessage
                  name="username"
                  component="div"
                  className="invalid-feedback"
                />
              </div>

              <div className="form-group">
                <label htmlFor="password">Password</label>
                <Field
                  name="password"
                  type="password"
                  className={
                    "form-control" +
                    (errors.password && touched.password ? " is-invalid" : "")
                  }
                />
                <ErrorMessage
                  name="password"
                  component="div"
                  className="invalid-feedback"
                />
              </div>

              <div className="form-group">
                <button
                  type="submit"
                  className="btn btn-primary btn-block"
                  disabled={loading}
                >
                  {loading && (
                    <span className="spinner-border spinner-border-sm"></span>
                  )}
                  <span>Login</span>
                </button>
              </div>
            </Form>
          )}
        </Formik>
      </div>

      {message && (
        <div className="form-group">
          <div className="alert alert-danger" role="alert">
            {message}
          </div>
        </div>
      )}
    </div>
  );
};

export default Login;

Register Page

This page is similar to Login Page.

For Form Validation, there are some more details:

  • username: required, between 3 and 20 characters
  • email: required, email format
  • password: required, between 6 and 40 characters

We’re gonna dispatch register action and show response message (successful or error).

components/Register.js

import React, { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Formik, Field, Form, ErrorMessage } from "formik";
import * as Yup from "yup";

import { register } from "../slices/auth";
import { clearMessage } from "../slices/message";

const Register = () => {
  const [successful, setSuccessful] = useState(false);

  const { message } = useSelector((state) => state.message);
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(clearMessage());
  }, [dispatch]);

  const initialValues = {
    username: "",
    email: "",
    password: "",
  };

  const validationSchema = Yup.object().shape({
    username: Yup.string()
      .test(
        "len",
        "The username must be between 3 and 20 characters.",
        (val) =>
          val && val.toString().length >= 3 && val.toString().length <= 20
      )
      .required("This field is required!"),
    email: Yup.string()
      .email("This is not a valid email.")
      .required("This field is required!"),
    password: Yup.string()
      .test(
        "len",
        "The password must be between 6 and 40 characters.",
        (val) =>
          val && val.toString().length >= 6 && val.toString().length <= 40
      )
      .required("This field is required!"),
  });

  const handleRegister = (formValue) => {
    const { username, email, password } = formValue;

    setSuccessful(false);

    dispatch(register({ username, email, password }))
      .unwrap()
      .then(() => {
        setSuccessful(true);
      })
      .catch(() => {
        setSuccessful(false);
      });
  };

  return (
    <div className="col-md-12 signup-form">
      <div className="card card-container">
        <img
          src="//ssl.gstatic.com/accounts/ui/avatar_2x.png"
          alt="profile-img"
          className="profile-img-card"
        />
        <Formik
          initialValues={initialValues}
          validationSchema={validationSchema}
          onSubmit={handleRegister}
        >
          {({ errors, touched }) => (
            <Form>
              {!successful && (
                <div>
                  <div className="form-group">
                    <label htmlFor="username">Username</label>
                    <Field
                      name="username"
                      type="text"
                      className={
                        "form-control" +
                        (errors.username && touched.username
                          ? " is-invalid"
                          : "")
                      }
                    />
                    <ErrorMessage
                      name="username"
                      component="div"
                      className="invalid-feedback"
                    />
                  </div>

                  <div className="form-group">
                    <label htmlFor="email">Email</label>
                    <Field
                      name="email"
                      type="email"
                      className={
                        "form-control" +
                        (errors.email && touched.email ? " is-invalid" : "")
                      }
                    />
                    <ErrorMessage
                      name="email"
                      component="div"
                      className="invalid-feedback"
                    />
                  </div>

                  <div className="form-group">
                    <label htmlFor="password">Password</label>
                    <Field
                      name="password"
                      type="password"
                      className={
                        "form-control" +
                        (errors.password && touched.password
                          ? " is-invalid"
                          : "")
                      }
                    />
                    <ErrorMessage
                      name="password"
                      component="div"
                      className="invalid-feedback"
                    />
                  </div>

                  <div className="form-group">
                    <button type="submit" className="btn btn-primary btn-block">
                      Sign Up
                    </button>
                  </div>
                </div>
              )}
            </Form>
          )}
        </Formik>
      </div>

      {message && (
        <div className="form-group">
          <div
            className={
              successful ? "alert alert-success" : "alert alert-danger"
            }
            role="alert"
          >
            {message}
          </div>
        </div>
      )}
    </div>
  );
};

export default Register;

Profile Page

This page gets current User from Local Storage by getting user in the application state and show user information.

components/Profile.js

import React from "react";
import { Navigate } from 'react-router-dom';
import { useSelector } from "react-redux";

const Profile = () => {
  const { user: currentUser } = useSelector((state) => state.auth);

  if (!currentUser) {
    return <Navigate to="/login" />;
  }

  return (
    <div className="container">
      <header className="jumbotron">
        <h3>
          <strong>{currentUser.username}</strong> Profile
        </h3>
      </header>
      <p>
        <strong>Id:</strong> {currentUser.id}
      </p>
      <p>
        <strong>Email:</strong> {currentUser.email}
      </p>
      <strong>Authorities:</strong>
      <ul>
        {currentUser.roles &&
          currentUser.roles.map((role, index) => <li key={index}>{role}</li>)}
      </ul>
    </div>
  );
};

export default Profile;

Create React Pages for accessing Resources

These pages will use UserService to request data from API.


components

Home.js

BoardUser.js

BoardModerator.js

BoardAdmin.js


Home Page

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

components/Home.js

import React, { useState, useEffect } from "react";

import UserService from "../services/user.service";

const Home = () => {
  const [content, setContent] = useState("");

  useEffect(() => {
    UserService.getPublicContent().then(
      (response) => {
        setContent(response.data);
      },
      (error) => {
        const _content =
          (error.response && error.response.data) ||
          error.message ||
          error.toString();

        setContent(_content);
      }
    );
  }, []);

  return (
    <div className="container">
      <header className="jumbotron">
        <h3>{content}</h3>
      </header>
    </div>
  );
};

export default Home;

Role-based Pages

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

  • BoardUser page calls UserService.getUserBoard()
  • BoardModerator page calls UserService.getModeratorBoard()
  • BoardAdmin page calls UserService.getAdminBoard()

I will show you User Page for example, other Pages are similar to this Page.

components/BoardUser.js

import React, { useState, useEffect } from "react";

import UserService from "../services/user.service";

const BoardUser = () => {
  const [content, setContent] = useState("");

  useEffect(() => {
    UserService.getUserBoard().then(
      (response) => {
        setContent(response.data);
      },
      (error) => {
        const _content =
          (error.response &&
            error.response.data &&
            error.response.data.message) ||
          error.message ||
          error.toString();

        setContent(_content);
      }
    );
  }, []);

  return (
    <div className="container">
      <header className="jumbotron">
        <h3>{content}</h3>
      </header>
    </div>
  );
};

export default BoardUser;

Add Navbar and define Routes

Now we add a navigation bar in App Page. This is the root container for our application.
The navbar dynamically changes by login status and current User’s roles.

  • Home: always
  • Login & Sign Up: if user hasn’t signed in yet
  • User: there is user value in the application state
  • Board Moderator: roles includes ROLE_MODERATOR
  • Board Admin: roles includes ROLE_ADMIN

src/App.js

import React, { useState, useEffect, useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";

import "bootstrap/dist/css/bootstrap.min.css";
import "./App.css";

import Login from "./app/components/Login";
import Register from "./app/components/Register";
import Home from "./app/components/Home";
import Profile from "./app/components/Profile";
import BoardUser from "./app/components/BoardUser";
import BoardModerator from "./app/components/BoardModerator";
import BoardAdmin from "./app/components/BoardAdmin";

import { logout } from "./app/slices/auth";

const App = () => {
  const [showModeratorBoard, setShowModeratorBoard] = useState(false);
  const [showAdminBoard, setShowAdminBoard] = useState(false);

  const { user: currentUser } = useSelector((state) => state.auth);
  const dispatch = useDispatch();

  const logOut = useCallback(() => {
    dispatch(logout());
  }, [dispatch]);

  useEffect(() => {
    if (currentUser) {
      setShowModeratorBoard(currentUser.roles.includes("ROLE_MODERATOR"));
      setShowAdminBoard(currentUser.roles.includes("ROLE_ADMIN"));
    } else {
      setShowModeratorBoard(false);
      setShowAdminBoard(false);
    }
  }, [currentUser]);

  return (
    <Router>
      <div>
        <nav className="navbar navbar-expand navbar-dark bg-dark">
          <Link to={"/"} className="navbar-brand">
            bezKoder
          </Link>
          <div className="navbar-nav mr-auto">
            <li className="nav-item">
              <Link to={"/home"} className="nav-link">
                Home
              </Link>
            </li>

            {showModeratorBoard && (
              <li className="nav-item">
                <Link to={"/mod"} className="nav-link">
                  Moderator Board
                </Link>
              </li>
            )}

            {showAdminBoard && (
              <li className="nav-item">
                <Link to={"/admin"} className="nav-link">
                  Admin Board
                </Link>
              </li>
            )}

            {currentUser && (
              <li className="nav-item">
                <Link to={"/user"} className="nav-link">
                  User
                </Link>
              </li>
            )}
          </div>

          {currentUser ? (
            <div className="navbar-nav ml-auto">
              <li className="nav-item">
                <Link to={"/profile"} className="nav-link">
                  {currentUser.username}
                </Link>
              </li>
              <li className="nav-item">
                <a href="/login" className="nav-link" onClick={logOut}>
                  LogOut
                </a>
              </li>
            </div>
          ) : (
            <div className="navbar-nav ml-auto">
              <li className="nav-item">
                <Link to={"/login"} className="nav-link">
                  Login
                </Link>
              </li>

              <li className="nav-item">
                <Link to={"/register"} className="nav-link">
                  Sign Up
                </Link>
              </li>
            </div>
          )}
        </nav>

        <div className="container mt-3">
          <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/home" element={<Home />} />
            <Route path="/login" element={<Login />} />
            <Route path="/register" element={<Register />} />
            <Route path="/profile" element={<Profile />} />
            <Route path="/user" element={<BoardUser />} />
            <Route path="/mod" element={<BoardModerator />} />
            <Route path="/admin" element={<BoardAdmin />} />
          </Routes>
        </div>
      </div>
    </Router>
  );
};

export default App;

You can simplify import statement with:
Absolute Import in React

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

Add CSS style for React Pages

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

label {
  display: block;
  margin-top: 10px;
}

.login-form,
.signup-form {
  max-width: 350px;
  margin: auto;
}

.card {
  background-color: #f7f7f7;
  padding: 20px 25px 30px;
  margin: 0 auto 25px;
  margin-top: 50px;
  -moz-border-radius: 2px;
  -webkit-border-radius: 2px;
  border-radius: 2px;
  -moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
  -webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
  box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
}

.profile-img-card {
  width: 96px;
  height: 96px;
  margin: 0 auto 10px;
  display: block;
  -moz-border-radius: 50%;
  -webkit-border-radius: 50%;
  border-radius: 50%;
}

Setup Proxy

The HttpOnly Cookie approach in this tutorial works if the React app and the back-end server hosted in same domain. So we need to use http-proxy-middleware for local development.

Run command:
npm install http-proxy-middleware
Or: yarn add http-proxy-middleware

In the src folder, create setupProxy.js file with following code:

const { createProxyMiddleware } = require("http-proxy-middleware");

module.exports = function (app) {
  app.use(
    "/api",
    createProxyMiddleware({
      target: "http://localhost:8080",
      changeOrigin: true,
    })
  );
};

Run the Project

You need to run one of following Rest API servers first:

  • Spring Boot Login and Registration example with H2
  • Spring Boot Login and Registration example with MySQL/PostgreSQL
  • Spring Boot Login and Registration example with MongoDB
  • Node.js Express Login and Registration example with MySQL/PostgreSQL
  • Node.js Express Login and Registration example with MongoDB

Then run this React Auth Application with command:
npm run start
Or: yarn start

Conclusion

Today we’ve done so many interesting things. I hope you understand the overall layers of our React Redux Login example (with Register and Logout) using Hooks, Redux-Toolkit, LocalStorage, React Router, 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 need Form Validation with React Hook Form 7, please visit:
React Form Validation with Hooks example

Add refresh token:
React + Redux: Refresh Token with Axios and JWT example

Happy learning, see you again!

Further Reading

  • React Router Guide
  • React Hooks
  • React Custom Hook
  • Redux Tutorials
  • Redux Toolkit Usage Guide
  • https://www.npmjs.com/package/formik
  • https://www.npmjs.com/package/yup
  • In-depth Introduction to JWT-JSON Web Token

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

Source Code

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

Without Redux:
React JWT Auth example

authentication authorization axios httponly cookie jwt local storage login react react hooks react router redux redux-toolkit registration security token based authentication

Post navigation

Angular 13 – Logout when Token is expired
Spring Boot Thymeleaf CRUD example

Follow us

  • Facebook
  • Youtube
  • Github

Tools

  • Json Formatter
  • .
  • Privacy Policy
  • Contact
  • About
DMCA.com Protection Status © 2019-2022 bezkoder.com
X