In this tutorial, we’re gonna build a React Redux Login, Register, Logout example with Redux Toolkit, React Router, Axios and Bootstrap using React Hooks. I will show you:
- JWT Authentication Flow for User Login, Register, Logout
- Project Structure for React Redux 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 Hooks: JWT Authentication (without Redux) example
Or without redux-toolkit:
React Redux Login, Logout, Registration example with Hooks
Overview of React Redux Login & Register 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:
– 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 Authorized account login (Moderator for example), the navigation bar will change:
– Check Browser Local Storage:
If you want to store JWT in HttpOnly Cookie, please visit:
React Redux Toolkit Authentication & Authorization example
– Check State in Redux:
If you want to add refresh token, please visit:
React + Redux: Refresh Token with Axios and JWT example
User Login & Register 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 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. 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.
If you want to store JWT in HttpOnly Cookie, please visit:
React Redux Toolkit Authentication & Authorization example
Technology
We’re gonna use these modules:
- React 18/17
- react-redux 8
- redux-toolkit 1.8.5
- react-router-dom 6
- axios 0.27.2
- Bootstrap 4
- formik 2.2.9
- yup 0.32.11
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.
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:
npx create-react-app react-redux-login-example
Then add Router Dom Module for later use with command:
– yarn add react-router-dom
Or: npm install react-router-dom
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/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:
yarn add axios
or 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");
};
const authService = {
register,
login,
logout,
};
export default authService;
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() });
};
const userService = {
getPublicContent,
getUserBoard,
getModeratorBoard,
getAdminBoard,
};
export default userService
You can see that we add a HTTP header with the help of authHeader()
function when requesting authorized resource.
Install react-redux and redux-toolkit
Install Redux-toolkit with the following command:
yarn add react-redux @reduxjs/toolkit
Or npm install react-redux @reduxjs/toolkit
With redux-toolkit, we don’t need to install redux-devtools-extension.
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/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()
:
store.js
import { configureStore } from '@reduxjs/toolkit'
import authReducer from "./slices/auth";
import messageReducer from "./slices/message";
const reducer = {
auth: authReducer,
message: messageReducer
}
const store = configureStore({
reducer: reducer,
devTools: true,
})
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 * as serviceWorker from "./serviceWorker";
const container = document.getElementById("root");
const root = createRoot(container);
root.render(
<Provider store={store}>
<App />
</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 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}
>
<Form>
<div className="form-group">
<label htmlFor="username">Username</label>
<Field name="username" type="text" className="form-control" />
<ErrorMessage
name="username"
component="div"
className="alert alert-danger"
/>
</div>
<div className="form-group">
<label htmlFor="password">Password</label>
<Field name="password" type="password" className="form-control" />
<ErrorMessage
name="password"
component="div"
className="alert alert-danger"
/>
</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 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, 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}
>
<Form>
{!successful && (
<div>
<div className="form-group">
<label htmlFor="username">Username</label>
<Field name="username" type="text" className="form-control" />
<ErrorMessage
name="username"
component="div"
className="alert alert-danger"
/>
</div>
<div className="form-group">
<label htmlFor="email">Email</label>
<Field name="email" type="email" className="form-control" />
<ErrorMessage
name="email"
component="div"
className="alert alert-danger"
/>
</div>
<div className="form-group">
<label htmlFor="password">Password</label>
<Field
name="password"
type="password"
className="form-control"
/>
<ErrorMessage
name="password"
component="div"
className="alert alert-danger"
/>
</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 (with token).
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;
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 "./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 "./slices/auth";
import EventBus from "./common/EventBus";
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);
}
EventBus.on("logout", () => {
logOut();
});
return () => {
EventBus.remove("logout");
};
}, [currentUser, logOut]);
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%;
}
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
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 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
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.
If you want to store JWT in HttpOnly Cookie, please visit:
React Redux Toolkit Authentication & Authorization example
Without redux-toolkit:
React Redux Login, Logout, Registration example with Hooks
can u explain more about eventbus function
Hi, you can read following tutorial:
Handle JWT Token expiration in React with Hooks
This helped me out a ton. Thanks
Dear Team,
Many thanks for the comprehensive front-end tutorial on React front end. Can you please create another tutorial explaining the integration of the front end and back end?
A) Frontend – React Redux JWT Authentication, Router, Axios
B) Backend – Node + Express + Mongodb + Mongoose
Hi, you can read following tutorial:
Login Page with React.js, MongoDB: MERN JWT Authentication example
Thanks
Best Tutorial! I have some questio here, why we still need to use window.location.reload(); after login successfull?
Hi there! This tutorial could not be written any better! Pretty sure he will have a good read. Many thanks for sharing!
Wonderful Tutorial! Thank you very much.
Good tutorial. Thanks!
This React tutorial truly made my day. You can not believe just how much time I had spent for this! Thanks!
I think every one is getting help from this tutorial. Thanks!