In this tutorial, we’re gonna build a React Redux Login, Logout, Registration example with LocalStorage, React Router, Axios and Bootstrap using React.js Hooks. I will show you:
- JWT Authentication Flow for User Registration & User Login, Logout
- Project Structure for React Redux JWT Authentication, LocalStorage, Router, Axios
- Working with Redux Actions, Reducers, Store for Application state
- 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 Custom Hook
– React Hooks CRUD example with Axios and Web API
– React Hooks File Upload example with Axios & Progress Bar
– React Form Validation with Hooks example
Fullstack (JWT Authentication & Authorization example):
– React + Spring Boot
– React + Node.js Express
The example without using Hooks (but React Components):
React Redux Login, Logout, Registration example
The example without using Redux:
React Hooks: JWT Authentication (without Redux) example
Overview of React Redux Registration & Login example
We will build a React.js application using Hooks 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:
– 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
User Registration and User Login Flow
For JWT Authentication, we’re gonna call 2 endpoints:
- POST
api/auth/signup
for User Registration - POST
api/auth/signin
for User Login
The following flow shows you an overview of Requests and Responses that React.js Client will make or receive. This React Client must add a JWT to HTTP Header before sending request to protected resources.
You can find step by step to implement these back-end servers in following tutorial:
- Spring Boot JWT Authentication with Spring Security, MySQL
- Spring Boot JWT Authentication with Spring Security, PostgreSQL
- Spring Boot JWT Authentication with Spring Security, MongoDB
- Node.js JWT Authentication & Authorization with MySQL
- Node.js JWT Authentication & Authorization with PostgreSQL
- Node.js JWT Authentication & Authorization with MongoDB
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.
– auth.service
methods use axios
to make HTTP requests. Its also store or get JWT 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
uses auth-header()
helper function to add JWT to HTTP header. auth-header()
returns an object containing the JWT of the currently logged in user from Local Storage.
Technology
We’re gonna use these modules:
- React 18/17
- react-redux 8.0.1
- redux 4.2.0
- redux-thunk 2.4.1
- react-router-dom 6
- axios 0.27.2
- react-validation 3.0.7
- Bootstrap 4
- validator 13.7.0
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
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: npm install react-router-dom
.
Open src/index.js, import BrowserRouter
and wrap the App
component:
import React from "react";
import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
const container = document.getElementById("root");
const root = createRoot(container);
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
Import Bootstrap
Run command: npm install bootstrap
.
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/services folder:
- Authentication service
- Data service
services
auth-header.js
auth.service.js (Authentication service)
user.service.js (Data service)
Before working with these services, we need to install Axios with command:
npm install axios
Authentication service
The service uses Axios for HTTP requests and Local Storage for user information & JWT.
It provides following important functions:
register()
: POST {username, email, password}login()
: POST {username, password} & saveJWT
to Local Storagelogout()
: removeJWT
from Local Storage
services/auth.service.js
import axios from "axios";
const API_URL = "http://localhost:8080/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.accessToken) {
localStorage.setItem("user", JSON.stringify(response.data));
}
return response.data;
});
};
const logout = () => {
localStorage.removeItem("user");
};
export default {
register,
login,
logout,
};
Data service
We also have methods for retrieving data from server. In the case we access protected resources, the HTTP request needs Authorization header.
Let’s create a helper function called authHeader()
inside auth-header.js:
export default function authHeader() {
const user = JSON.parse(localStorage.getItem('user'));
if (user && user.accessToken) {
return { Authorization: 'Bearer ' + user.accessToken };
} else {
return {};
}
}
The code above checks Local Storage for user
item. If there is a logged in user
with accessToken
(JWT), return HTTP Authorization header. Otherwise, return an empty object.
Note: For Node.js Express back-end, please use x-access-token header like this:
export default function authHeader() {
const user = JSON.parse(localStorage.getItem('user'));
if (user && user.accessToken) {
// for Node.js Express back-end
return { 'x-access-token': user.accessToken };
} else {
return {};
}
}
Now we define a service for accessing data in services/user.service.js:
import axios from "axios";
import authHeader from "./auth-header";
const API_URL = "http://localhost:8080/api/test/";
const getPublicContent = () => {
return axios.get(API_URL + "all");
};
const getUserBoard = () => {
return axios.get(API_URL + "user", { headers: authHeader() });
};
const getModeratorBoard = () => {
return axios.get(API_URL + "mod", { headers: authHeader() });
};
const getAdminBoard = () => {
return axios.get(API_URL + "admin", { headers: authHeader() });
};
export default {
getPublicContent,
getUserBoard,
getModeratorBoard,
getAdminBoard,
};
You can see that we add a HTTP header with the help of authHeader()
function when requesting authorized resource.
Create Redux Actions
We’re gonna create two kind of actions in src/actions folder:
actions
types.js
auth.js (register/login/logout actions)
message.js (set/clear message actions)
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";
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,
});
Auth Actions Creator
This is creator for actions related to authentication. 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
REGISTER_SUCCESS
andSET_MESSAGE
if successful - dispatch
REGISTER_FAIL
andSET_MESSAGE
if failed
– login()
- calls the AuthService.login(username, password)
- dispatch
LOGIN_SUCCESS
andSET_MESSAGE
if successful - dispatch
LOGIN_FAIL
andSET_MESSAGE
if failed
Both action creators return a Promise
for Components using them.
actions/auth.js
import {
REGISTER_SUCCESS,
REGISTER_FAIL,
LOGIN_SUCCESS,
LOGIN_FAIL,
LOGOUT,
SET_MESSAGE,
} from "./types";
import AuthService from "../services/auth.service";
export const register = (username, email, password) => (dispatch) => {
return AuthService.register(username, email, password).then(
(response) => {
dispatch({
type: REGISTER_SUCCESS,
});
dispatch({
type: SET_MESSAGE,
payload: response.data.message,
});
return Promise.resolve();
},
(error) => {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
dispatch({
type: REGISTER_FAIL,
});
dispatch({
type: SET_MESSAGE,
payload: message,
});
return Promise.reject();
}
);
};
export const login = (username, password) => (dispatch) => {
return AuthService.login(username, password).then(
(data) => {
dispatch({
type: LOGIN_SUCCESS,
payload: { user: data },
});
return Promise.resolve();
},
(error) => {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
dispatch({
type: LOGIN_FAIL,
});
dispatch({
type: SET_MESSAGE,
payload: message,
});
return Promise.reject();
}
);
};
export const logout = () => (dispatch) => {
AuthService.logout();
dispatch({
type: LOGOUT,
});
};
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.
reducers
index.js
auth.js (register/login/logout)
message.js (set/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;
}
}
Auth Reducer
The Auth reducer will update the isLoggedIn
and user
state of the application.
reducers/auth.js
import {
REGISTER_SUCCESS,
REGISTER_FAIL,
LOGIN_SUCCESS,
LOGIN_FAIL,
LOGOUT,
} from "../actions/types";
const user = JSON.parse(localStorage.getItem("user"));
const initialState = user
? { isLoggedIn: true, user }
: { isLoggedIn: false, user: null };
export default function (state = initialState, action) {
const { type, payload } = action;
switch (type) {
case REGISTER_SUCCESS:
return {
...state,
isLoggedIn: false,
};
case REGISTER_FAIL:
return {
...state,
isLoggedIn: false,
};
case LOGIN_SUCCESS:
return {
...state,
isLoggedIn: true,
user: payload.user,
};
case LOGIN_FAIL:
return {
...state,
isLoggedIn: false,
user: null,
};
case LOGOUT:
return {
...state,
isLoggedIn: false,
user: null,
};
default:
return state;
}
}
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 Redux Store
This Store will bring Actions and Reducers together and hold the Application state.
Now we need to install Redux, Thunk Middleware and Redux Devtool Extension.
Run the command:
npm install redux redux-thunk
npm install --save-dev redux-devtools-extension
In the previous section, we used combineReducers()
to combine 2 reducers into one. Let’s import it, and pass it to createStore()
:
store.js
import { createStore, applyMiddleware } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import thunk from "redux-thunk";
import rootReducer from "./reducers";
const middleware = [thunk];
const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
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 "./store";
import "./index.css";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
const container = document.getElementById("root");
const root = createRoot(container);
root.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
);
Create React Pages for Authentication
In src 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 react-validation library to our project.
Run the command: npm install react-validation validator
To use react-validation in this example, you need to import following items:
import Form from "react-validation/build/form";
import Input from "react-validation/build/input";
import CheckButton from "react-validation/build/button";
import { isEmail } from "validator";
We also use isEmail()
function from validator to verify email.
This is how we put them in render()
method with validations
attribute:
const required = value => {
if (!value) {
return (
<div className="alert alert-danger" role="alert">
This field is required!
</div>
);
}
};
const email = value => {
if (!isEmail(value)) {
return (
<div className="alert alert-danger" role="alert">
This is not a valid email.
</div>
);
}
};
render() {
return (
...
<Form
onSubmit={handleLogin}
ref={form}
>
...
<Input
type="text"
className="form-control"
...
validations={[required, email]}
/>
<CheckButton
style={{ display: "none" }}
ref={checkBtn}
/>
</Form>
...
);
}
We’re gonna call Form validateAll()
method to check validation functions in validations
. Then CheckButton
helps us to verify if the form validation is successful or not. So this button will not display on the form.
form.validateAll();
if (checkBtn.context._errors.length === 0) {
// do something when no error
}
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, useRef } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Navigate, useNavigate } from 'react-router-dom';
import Form from "react-validation/build/form";
import Input from "react-validation/build/input";
import CheckButton from "react-validation/build/button";
import { login } from "../actions/auth";
const required = (value) => {
if (!value) {
return (
<div className="alert alert-danger" role="alert">
This field is required!
</div>
);
}
};
const Login = (props) => {
let navigate = useNavigate();
const form = useRef();
const checkBtn = useRef();
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [loading, setLoading] = useState(false);
const { isLoggedIn } = useSelector(state => state.auth);
const { message } = useSelector(state => state.message);
const dispatch = useDispatch();
const onChangeUsername = (e) => {
const username = e.target.value;
setUsername(username);
};
const onChangePassword = (e) => {
const password = e.target.value;
setPassword(password);
};
const handleLogin = (e) => {
e.preventDefault();
setLoading(true);
form.current.validateAll();
if (checkBtn.current.context._errors.length === 0) {
dispatch(login(username, password))
.then(() => {
navigate("/profile");
window.location.reload();
})
.catch(() => {
setLoading(false);
});
} else {
setLoading(false);
}
};
if (isLoggedIn) {
return <Navigate to="/profile" />;
}
return (
<div className="col-md-12">
<div className="card card-container">
<img
src="//ssl.gstatic.com/accounts/ui/avatar_2x.png"
alt="profile-img"
className="profile-img-card"
/>
<Form onSubmit={handleLogin} ref={form}>
<div className="form-group">
<label htmlFor="username">Username</label>
<Input
type="text"
className="form-control"
name="username"
value={username}
onChange={onChangeUsername}
validations={[required]}
/>
</div>
<div className="form-group">
<label htmlFor="password">Password</label>
<Input
type="password"
className="form-control"
name="password"
value={password}
onChange={onChangePassword}
validations={[required]}
/>
</div>
<div className="form-group">
<button className="btn btn-primary btn-block" disabled={loading}>
{loading && (
<span className="spinner-border spinner-border-sm"></span>
)}
<span>Login</span>
</button>
</div>
{message && (
<div className="form-group">
<div className="alert alert-danger" role="alert">
{message}
</div>
</div>
)}
<CheckButton style={{ display: "none" }} ref={checkBtn} />
</Form>
</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 charactersemail
: required, email formatpassword
: 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, useRef } from "react";
import { useDispatch, useSelector } from "react-redux";
import Form from "react-validation/build/form";
import Input from "react-validation/build/input";
import CheckButton from "react-validation/build/button";
import { isEmail } from "validator";
import { register } from "../actions/auth";
const required = (value) => {
if (!value) {
return (
<div className="alert alert-danger" role="alert">
This field is required!
</div>
);
}
};
const validEmail = (value) => {
if (!isEmail(value)) {
return (
<div className="alert alert-danger" role="alert">
This is not a valid email.
</div>
);
}
};
const vusername = (value) => {
if (value.length < 3 || value.length > 20) {
return (
<div className="alert alert-danger" role="alert">
The username must be between 3 and 20 characters.
</div>
);
}
};
const vpassword = (value) => {
if (value.length < 6 || value.length > 40) {
return (
<div className="alert alert-danger" role="alert">
The password must be between 6 and 40 characters.
</div>
);
}
};
const Register = () => {
const form = useRef();
const checkBtn = useRef();
const [username, setUsername] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [successful, setSuccessful] = useState(false);
const { message } = useSelector(state => state.message);
const dispatch = useDispatch();
const onChangeUsername = (e) => {
const username = e.target.value;
setUsername(username);
};
const onChangeEmail = (e) => {
const email = e.target.value;
setEmail(email);
};
const onChangePassword = (e) => {
const password = e.target.value;
setPassword(password);
};
const handleRegister = (e) => {
e.preventDefault();
setSuccessful(false);
form.current.validateAll();
if (checkBtn.current.context._errors.length === 0) {
dispatch(register(username, email, password))
.then(() => {
setSuccessful(true);
})
.catch(() => {
setSuccessful(false);
});
}
};
return (
<div className="col-md-12">
<div className="card card-container">
<img
src="//ssl.gstatic.com/accounts/ui/avatar_2x.png"
alt="profile-img"
className="profile-img-card"
/>
<Form onSubmit={handleRegister} ref={form}>
{!successful && (
<div>
<div className="form-group">
<label htmlFor="username">Username</label>
<Input
type="text"
className="form-control"
name="username"
value={username}
onChange={onChangeUsername}
validations={[required, vusername]}
/>
</div>
<div className="form-group">
<label htmlFor="email">Email</label>
<Input
type="text"
className="form-control"
name="email"
value={email}
onChange={onChangeEmail}
validations={[required, validEmail]}
/>
</div>
<div className="form-group">
<label htmlFor="password">Password</label>
<Input
type="password"
className="form-control"
name="password"
value={password}
onChange={onChangePassword}
validations={[required, vpassword]}
/>
</div>
<div className="form-group">
<button className="btn btn-primary btn-block">Sign Up</button>
</div>
</div>
)}
{message && (
<div className="form-group">
<div className={ successful ? "alert alert-success" : "alert alert-danger" } role="alert">
{message}
</div>
</div>
)}
<CheckButton style={{ display: "none" }} ref={checkBtn} />
</Form>
</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 (with token).
If the user
is not logged in, navigate to /login
page.
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>Token:</strong> {currentUser.accessToken.substring(0, 20)} ...{" "}
{currentUser.accessToken.substr(currentUser.accessToken.length - 20)}
</p>
<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 React Router
Modify App Page
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 { Routes, Route, Link, useLocation } from "react-router-dom";
import "bootstrap/dist/css/bootstrap.min.css";
import "./App.css";
import Login from "./components/Login";
import Register from "./components/Register";
import Home from "./components/Home";
import Profile from "./components/Profile";
import BoardUser from "./components/BoardUser";
import BoardModerator from "./components/BoardModerator";
import BoardAdmin from "./components/BoardAdmin";
import { logout } from "./actions/auth";
import { clearMessage } from "./actions/message";
const App = () => {
const [showModeratorBoard, setShowModeratorBoard] = useState(false);
const [showAdminBoard, setShowAdminBoard] = useState(false);
const { user: currentUser } = useSelector((state) => state.auth);
const dispatch = useDispatch();
let location = useLocation();
useEffect(() => {
if (["/login", "/register"].includes(location.pathname)) {
dispatch(clearMessage()); // clear message when changing location
}
}, [dispatch, location]);
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 (
<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>
);
};
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;
}
.card-container.card {
max-width: 350px !important;
padding: 40px 40px;
}
.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%;
}
Configure Port for React JWT Auth Client with Web API
Because most of HTTP Server use CORS configuration that accepts resource sharing restricted to some sites or ports, so we also need to configure port for our App.
In project folder, create .env file with following content:
PORT=8081
Now we’ve set our app running at port 8081
. You will need to do this work if you use one of following Servers:
- Spring Boot JWT Authentication with Spring Security, MySQL
- Spring Boot JWT Authentication with Spring Security, PostgreSQL
- Spring Boot JWT Authentication with Spring Security, MongoDB
- Node.js JWT Authentication & Authorization with MySQL
- Node.js JWT Authentication & Authorization with PostgreSQL
- Node.js JWT Authentication & Authorization with MongoDB
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!
Further Reading
- React Router Guide
- React Hooks
- React Custom Hook
- Redux Tutorials
- https://www.npmjs.com/package/react-validation
- https://www.npmjs.com/package/validator
- 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.
Using redux-toolkit: React Redux Login, Register example with redux-toolkit & Hooks
we learn so many in few time. thank a lot !
Please update this tutorial bacause react-router-dom modified some functions. Redirect => useNavigate and Switch => Routes
Hi, I’ve just updated the tutorial with latest react-router-dom 🙂
Incredible! This React with Redux tutorial is exactly what I am looking for!
Thanks very useful React tutorial!
I love your tutorials!! I have used a couple to practice my React and server building skills!
One question: for the context, I’m getting errors. Do you happen to have a tutorial that you can refer me to that uses createContext or something instead?
Thank you so much!!
Yvette
Hello everyone,
I am getting a blank screen at port 8081
This is what I am getting from the client when I run it:
“`
Compiled successfully!
You can now view fe-cwc in the browser.
Local: http://localhost:8081
On Your Network: http://10.0.0.9:8081
Note that the development build is not optimized.
To create a production build, use npm run build.
asset static/js/bundle.js 3 MiB [emitted] (name: main) 1 related asset
asset index.html 1.67 KiB [emitted]
asset asset-manifest.json 190 bytes [emitted]
cached modules 2.72 MiB (javascript) 28.2 KiB (runtime) [cached] 340 modules
webpack 5.65.0 compiled successfully in 648 ms
“`
and this from running my server ( I run the server first and then add the client):
“`
yarn run v1.22.17
$ nodemon app/server.js
[nodemon] 2.0.15
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node app/server.js server.js`
Server is running on port 8080.
Executing (default): DROP TABLE IF EXISTS “user_roles” CASCADE;
Executing (default): DROP TABLE IF EXISTS “roles” CASCADE;
Executing (default): DROP TABLE IF EXISTS “users” CASCADE;
Executing (default): DROP TABLE IF EXISTS “users” CASCADE;
…. truncated. Here goes all the sql part
“`
The backend works great through postman.
Any advise on what I should be looking at to fix this?
Thank you
Hi everyone! In this tutorial i’m using django as backend.
So, in my src/services/auth-header.js
// return { Authorization: “Bearer ” + user.accessToken }; how can i change line if it’s django backend.
Thank you for your help.
Hi, it depends on how you implement the way backend get
accessToken
for validating.Thanks on your marvelous tutorial! I genuinely enjoyed reading it!
Hi, can you please explain how to add admin/ moderator roles if possible. Appriciated
Edit: i am using React with Node JS and MongoDB, everything is working perfectly except having trouble understanding how to add roles. Thanks
Hey man!
Great Tutorial, helps me a lot. 🙂
I used this for my front-end, for my backend I used Node.js JWT example (of course also from here :P).
After “combining” them with the tutorial “Integrate React with Node.js” i stumbled over a 404 Error when refreshing.
In my server. js I set up
const path = __dirname + “/app/views/”;
app.use(express.static(path));
app.get(‘/’, function (req, res) {
res.sendFile(path + “index.html”);
});
as mentioned in the Tutorial, but in the index.js where we set HashRouter (like in the Tutorial) I can not do this because we use Redux here with
What can I do to avoid getting the 404 Error on refresh or new loading after production build when using Redux?
Thanks a lot in advance! 🙂
EDIT: “…we use Redux here Provider store={store}..”
Hello, this tutorial is awesome. Thanks!
Good tutorial! God Bless you man. Have a great day.
Thanks!
You have touched some nice points here. Any way keep up writing tutorials. Thanks!
You managed to write the tutorial upon the top and also defined out the whole thing easily to read. Will probably be back to read more React tutorials.
Thanks!
thanks so much
What is your API structure?
can you upload the JSON file
Great tutorial!
Hello
I have an issue, when I got login successfully, app logout automatically as currentuser is null.
what is the reason for this?
Regards
Hi
Nice article. would you plz explain this line?
I got this error on the above line “Cannot read property ‘includes’ of undefined”
thanks
Finally I have found the React tutorial which helped me. Many thanks!
Very nice tutorial. I absolutely love your website.
Stick with it!
Hi, Thank you for cool! tutorial, I try to integrate this frontend with backend (Node.js + MongoDB: User Authentication & Authorization with JWT).
I did yarn build and copy all files from React build folder to app/views folder.
That’s OK.
But I need your suggestion to solve problem for “404 Not found on Refresh”.
Thanks in advance for your support.
Hi, you can read this tutorial:
Integrate React with Node.js Express on same Server/Port
I think the you are actually working hard for this React Redux tutorial. Thanks!
Awesome! It really helped a lot. Could you please tell me how to write this tutorial using react-cookie?
Hi, I will write the tutorial when having time 🙂 For using cookies, we need to modify place to store the JWT and attach it to HTTP requests.
Very Nice tutorial and good to understand, Could you upload or tell how to use cookies instead of local storage.
Hi, currently you can read following tutorial, I will write a new tutorial for React + Redux soon 🙂
React.js Login & Registration example – JWT & HttpOnly Cookie
Error: could not find react-redux context value; please ensure the component is wrapped in a
â–¼ 29 stack frames were expanded.
useReduxContext
while running the above code finding this issue plz support me..
Hi. replace the code with this in your index.js file.
ReactDOM.render(
,
document.getElementById(‘root’)
);
Thank you. This React tutorial is really helpful!!
This is an excellent resource. The pieces were well-connected and simplified.
However, is it safe to use localstorage for authentication logic?
Hi, this can be vulnerable to cross-site scripting (XSS) attacks. You can use Cookies with the HttpOnly cookie flag which are not accessible through JavaScript, and are immune to XSS.
Could you explain how to use it ?
Would it change much of the code logic?
Beginner here. Btw, thanks for the amazing tutorial!
Highly energetic tutorial, I loved it a lot. Will there be server?
Hi, I mention tutorials for servers in the tutorial.
Thanks a lot.
hello! I really like this tutorial so much!
Thank you for the good tutorial.
THANK YOU SO MUCH!
Many thanks!
Kindly create one where we are using django .
Hi @bezkoder
Great tutorial! I have a small problem though. I’ve noticed that you didn’t use store.js anywhere in the tutorial. I tried to pass it to the provider in the root index.js but I get an error telling me that I exported something wrong. Can you help me, how to use the store properly?
import React from “react”;
import ReactDOM from “react-dom”;
import App from “./App”;
import { Provider } from “react-redux”;
import store from “./store”;
ReactDOM.render(
,
document.getElementById(“root”)
);
ReactDOM.render(
,
document.getElementById(“root”)
);
I noticed this same thing too. Hopefully you were able to figure it out already but, for anyone else who is wondering, here is what I added to `index.js` to connect the store. Hope it helps!
`
…
import { Provider } from ‘react-redux’;
…
import store from ‘./store’;
ReactDOM.render(
,
document.getElementById(‘root’)
);
`
Thank you very much!
This tutorial is very clear and professional!
HI @bezkoder
I am getting Request failed with status code 404. Could you please suggest me how to solve this isse?
Hi, you should use one of backend servers that I mention in the tutorial.
Could you please add a snippet of this. Some of us are beginners and this step would really help
I have Network Error when I launch the app. What may be the problem?
Hi, you should run one of the backend server I mentioned in the tutorial. 🙂
hii can i get the backend server for this example ?
Yes, in the tutorial, I’ve mentioned some backend servers 🙂
yes you can do it
Great tutorial, thank you so much the time you put into this.
Just one thing, you didn’t include the Provider wrapper for the redux store. I think it’s pretty clear where to put it when you already have done it once, but I might be confusing for beginners.
Cheers
Great post! Very clear about how all those stack are linked to each other! very well structured! thank you
Hello Great Tutorial!
What is about a solution with JWT refresh token?
Will this be more secure?
Regards
I would be interested in this too! Maybe even a tutorial that includes refresh tokens
Hi, here you are: React + Redux: Refresh Token with Axios and JWT example
Hi, here you are: React + Redux: Refresh Token with Axios and JWT example
Thank you for all your tutorials! Keep up the great work!
Great post. I was checking constantly this blog and I am impressed with your JWT Authentication tutorial.
Thanks a lot for this Redux Auth tutorial!