In this tutorial, we’re gonna build a React Redux Token Authentication example with JWT, LocalStorage, React Router, Axios and Bootstrap. I will show you:
- JWT Authentication Flow for User Signup & User Login
- Project Structure for React Redux JWT Authentication, LocalStorage, Router, Axios
- Working with Redux Actions, Reducers, Store for Application state
- Creating React Components with Form Validation
- React Components for accessing protected Resources (Authorization)
- Dynamic Navigation Bar in React App
Related Posts:
– In-depth Introduction to JWT-JSON Web Token
– React.js CRUD example to consume Web API
– React (with Redux) CRUD example to consume Web API
– React File Upload with Axios and Progress Bar to Rest API
– React JWT Authentication (without Redux) example
Fullstack (JWT Authentication & Authorization example):
– React + Spring Boot
– React + Node.js Express + MySQL/PostgreSQL
– React + Node.js Express + MongoDB
The example using React Hooks:
React Hooks + Redux: JWT Authentication & Authorization example
Contents
- Overview of React Redux JWT Authentication example
- User Registration and User Login Flow
- React Component Diagram with Redux, Router, Axios
- Technology
- Project Structure
- Setup React.js Project
- Import Bootstrap
- Create Services
- Create Redux Actions
- Create Redux Reducers
- Create Redux Store
- Create React Components for Authentication
- Create React Components for accessing Resources
- Add Navbar and define Routes
- Logout when the Token is expired
- Add CSS style for React Components
- Configure Port for React JWT Auth Client with Web API
- Conclusion
- Further Reading
- Source Code
Overview of React Redux JWT Authentication example
We will build a React application 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:
– Signup failed:
– Form Validation Support:
– Login Page:
– Profile Page (for successful Login):
– For Moderator account login, the navigation bar will change by authorities:
– Check Browser Local Storage:
– 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 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 MongoDB
- Node.js JWT Authentication & Authorization with PostgreSQL
React Component Diagram with Redux, Router, Axios
Let’s look at the diagram below.
– The App
component 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
components 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
component is public for all visitor.
– Profile
component displays user information after the login action is successful.
– BoardUser
, BoardModerator
, BoardAdmin
components will be displayed in navbar by state user.roles
. In these components, 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 16
- react-redux 7.2.1
- redux 4.0.5
- redux-thunk 2.3.0
- react-router-dom 5
- axios 0.19.2
- react-validation 3.0.7
- Bootstrap 4
- validator 13.1.1
Project Structure
This is folders & files structure for this React 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.
Setup React.js Project
Open cmd at the folder you want to save Project folder, run command:
npx create-react-app react-redux-jwt-auth
Then add Router Dom Module for later use: npm install react-router-dom
.
Import Bootstrap
Run command: npm install bootstrap
.
Open src/App.js and modify the code inside it as following-
import React, { Component } from "react";
import "bootstrap/dist/css/bootstrap.min.css";
class App extends Component {
render() {
// ...
}
}
export default App;
Create Services
We’re gonna create two services in src/services folder:
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 methods:
login()
: POST {username, password} & saveJWT
to Local Storagelogout()
: removeJWT
from Local Storageregister()
: POST {username, email, password}
import axios from "axios";
const API_URL = "http://localhost:8080/api/auth/";
class AuthService {
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;
});
}
logout() {
localStorage.removeItem("user");
}
register(username, email, password) {
return axios.post(API_URL + "signup", {
username,
email,
password,
});
}
}
export default new 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 user.service.js:
import axios from 'axios';
import authHeader from './auth-header';
const API_URL = 'http://localhost:8080/api/test/';
class UserService {
getPublicContent() {
return axios.get(API_URL + 'all');
}
getUserBoard() {
return axios.get(API_URL + 'user', { headers: authHeader() });
}
getModeratorBoard() {
return axios.get(API_URL + 'mod', { headers: authHeader() });
}
getAdminBoard() {
return axios.get(API_URL + 'admin', { headers: authHeader() });
}
}
export default new UserService();
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;
Create React Components for Authentication
In src folder, create new folder named components and add several files as following:
components
login.component.js
register.component.js
profile.component.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={this.handleLogin}
ref={c => {this.form = c;}}
>
...
<Input
type="text"
className="form-control"
...
validations={[required, email]}
/>
<CheckButton
style={{ display: "none" }}
ref={c => {this.checkBtn = c;}}
/>
</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.
this.form.validateAll();
if (this.checkBtn.context._errors.length === 0) {
// do something when no error
}
Another way to implement Form Validation:
React Form Validation example with Formik and Yup
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: this.props.history.push("/profile");
.
For the application state
, we use Redux connect()
function with mapStateToProps
:
– redirect user to Profile page by checking isLoggedIn
– show response message with message
login.component.js
import React, { Component } from "react";
import { Redirect } 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 { connect } from "react-redux";
import { login } from "../actions/auth";
const required = (value) => {
if (!value) {
return (
<div className="alert alert-danger" role="alert">
This field is required!
</div>
);
}
};
class Login extends Component {
constructor(props) {
super(props);
this.handleLogin = this.handleLogin.bind(this);
this.onChangeUsername = this.onChangeUsername.bind(this);
this.onChangePassword = this.onChangePassword.bind(this);
this.state = {
username: "",
password: "",
loading: false,
};
}
onChangeUsername(e) {
this.setState({
username: e.target.value,
});
}
onChangePassword(e) {
this.setState({
password: e.target.value,
});
}
handleLogin(e) {
e.preventDefault();
this.setState({
loading: true,
});
this.form.validateAll();
const { dispatch, history } = this.props;
if (this.checkBtn.context._errors.length === 0) {
dispatch(login(this.state.username, this.state.password))
.then(() => {
history.push("/profile");
window.location.reload();
})
.catch(() => {
this.setState({
loading: false
});
});
} else {
this.setState({
loading: false,
});
}
}
render() {
const { isLoggedIn, message } = this.props;
if (isLoggedIn) {
return <Redirect 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={this.handleLogin}
ref={(c) => {
this.form = c;
}}
>
<div className="form-group">
<label htmlFor="username">Username</label>
<Input
type="text"
className="form-control"
name="username"
value={this.state.username}
onChange={this.onChangeUsername}
validations={[required]}
/>
</div>
<div className="form-group">
<label htmlFor="password">Password</label>
<Input
type="password"
className="form-control"
name="password"
value={this.state.password}
onChange={this.onChangePassword}
validations={[required]}
/>
</div>
<div className="form-group">
<button
className="btn btn-primary btn-block"
disabled={this.state.loading}
>
{this.state.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={(c) => {
this.checkBtn = c;
}}
/>
</Form>
</div>
</div>
);
}
}
function mapStateToProps(state) {
const { isLoggedIn } = state.auth;
const { message } = state.message;
return {
isLoggedIn,
message
};
}
export default connect(mapStateToProps)(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).
register.component.js
import React, { Component } from "react";
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 { connect } from "react-redux";
import { register } from "../actions/auth";
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>
);
}
};
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>
);
}
};
class Register extends Component {
constructor(props) {
super(props);
this.handleRegister = this.handleRegister.bind(this);
this.onChangeUsername = this.onChangeUsername.bind(this);
this.onChangeEmail = this.onChangeEmail.bind(this);
this.onChangePassword = this.onChangePassword.bind(this);
this.state = {
username: "",
email: "",
password: "",
successful: false,
};
}
onChangeUsername(e) {
this.setState({
username: e.target.value,
});
}
onChangeEmail(e) {
this.setState({
email: e.target.value,
});
}
onChangePassword(e) {
this.setState({
password: e.target.value,
});
}
handleRegister(e) {
e.preventDefault();
this.setState({
successful: false,
});
this.form.validateAll();
if (this.checkBtn.context._errors.length === 0) {
this.props
.dispatch(
register(this.state.username, this.state.email, this.state.password)
)
.then(() => {
this.setState({
successful: true,
});
})
.catch(() => {
this.setState({
successful: false,
});
});
}
}
render() {
const { message } = this.props;
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={this.handleRegister}
ref={(c) => {
this.form = c;
}}
>
{!this.state.successful && (
<div>
<div className="form-group">
<label htmlFor="username">Username</label>
<Input
type="text"
className="form-control"
name="username"
value={this.state.username}
onChange={this.onChangeUsername}
validations={[required, vusername]}
/>
</div>
<div className="form-group">
<label htmlFor="email">Email</label>
<Input
type="text"
className="form-control"
name="email"
value={this.state.email}
onChange={this.onChangeEmail}
validations={[required, email]}
/>
</div>
<div className="form-group">
<label htmlFor="password">Password</label>
<Input
type="password"
className="form-control"
name="password"
value={this.state.password}
onChange={this.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={ this.state.successful ? "alert alert-success" : "alert alert-danger" } role="alert">
{message}
</div>
</div>
)}
<CheckButton
style={{ display: "none" }}
ref={(c) => {
this.checkBtn = c;
}}
/>
</Form>
</div>
</div>
);
}
}
function mapStateToProps(state) {
const { message } = state.message;
return {
message,
};
}
export default connect(mapStateToProps)(Register);
Profile Page
This page gets current User from Local Storage by getting user
in the application state and show user information (with token).
profile.component.js
import React, { Component } from "react";
import { Redirect } from 'react-router-dom';
import { connect } from "react-redux";
class Profile extends Component {
render() {
const { user: currentUser } = this.props;
if (!currentUser) {
return <Redirect 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>
);
}
}
function mapStateToProps(state) {
const { user } = state.auth;
return {
user,
};
}
export default connect(mapStateToProps)(Profile);
Create React Components for accessing Resources
These components will use UserService
to request data from API.
components
home.component.js
board-user.component.js
board-moderator.component.js
board-admin.component.js
Home Page
This is a public page that shows public content. People don’t need to log in to view this page.
home.component.js
import React, { Component } from "react";
import UserService from "../services/user.service";
export default class Home extends Component {
constructor(props) {
super(props);
this.state = {
content: ""
};
}
componentDidMount() {
UserService.getPublicContent().then(
response => {
this.setState({
content: response.data
});
},
error => {
this.setState({
content:
(error.response && error.response.data) ||
error.message ||
error.toString()
});
}
);
}
render() {
return (
<div className="container">
<header className="jumbotron">
<h3>{this.state.content}</h3>
</header>
</div>
);
}
}
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.
board-user.component.js
import React, { Component } from "react";
import UserService from "../services/user.service";
export default class BoardUser extends Component {
constructor(props) {
super(props);
this.state = {
content: ""
};
}
componentDidMount() {
UserService.getUserBoard().then(
response => {
this.setState({
content: response.data
});
},
error => {
this.setState({
content:
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString()
});
}
);
}
render() {
return (
<div className="container">
<header className="jumbotron">
<h3>{this.state.content}</h3>
</header>
</div>
);
}
}
You can simplify import statement with:
Absolute Import in React
Create React Router History
This is a custom history object used by the React Router.
helpers/history.js
import { createBrowserHistory } from "history";
export const history = createBrowserHistory();
Modify App Component
Now we add a navigation bar in App
component. 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, { Component } from "react";
import { connect } from "react-redux";
import { Router, Switch, Route, Link } from "react-router-dom";
import "bootstrap/dist/css/bootstrap.min.css";
import "./App.css";
import Login from "./components/login.component";
import Register from "./components/register.component";
import Home from "./components/home.component";
import Profile from "./components/profile.component";
import BoardUser from "./components/board-user.component";
import BoardModerator from "./components/board-moderator.component";
import BoardAdmin from "./components/board-admin.component";
import { logout } from "./actions/auth";
import { clearMessage } from "./actions/message";
import { history } from './helpers/history';
class App extends Component {
constructor(props) {
super(props);
this.logOut = this.logOut.bind(this);
this.state = {
showModeratorBoard: false,
showAdminBoard: false,
currentUser: undefined,
};
history.listen((location) => {
props.dispatch(clearMessage()); // clear message when changing location
});
}
componentDidMount() {
const user = this.props.user;
if (user) {
this.setState({
currentUser: user,
showModeratorBoard: user.roles.includes("ROLE_MODERATOR"),
showAdminBoard: user.roles.includes("ROLE_ADMIN"),
});
}
}
logOut() {
this.props.dispatch(logout());
}
render() {
const { currentUser, showModeratorBoard, showAdminBoard } = this.state;
return (
<Router history={history}>
<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={this.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">
<Switch>
<Route exact path={["/", "/home"]} component={Home} />
<Route exact path="/login" component={Login} />
<Route exact path="/register" component={Register} />
<Route exact path="/profile" component={Profile} />
<Route path="/user" component={BoardUser} />
<Route path="/mod" component={BoardModerator} />
<Route path="/admin" component={BoardAdmin} />
</Switch>
</div>
</div>
</Router>
);
}
}
function mapStateToProps(state) {
const { user } = state.auth;
return {
user,
};
}
export default connect(mapStateToProps)(App);
Logout when the Token is expired
There are two ways. For more details, please visit:
React – How to Logout when JWT Token is expired
Add CSS style for React Components
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 retricted 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 MongoDB
- Node.js JWT Authentication & Authorization with PostgreSQL
Conclusion
Congratulation!
Today we’ve done so many interesting things. I hope you understand the overall layers of our React Redux JWT Authentication App using LocalStorage, React Router, Thunk Middleware, Axios, Bootstrap. Now you can apply it in your project at ease.
You should continue to check if Token is expired and logout:
React – How to Logout when JWT Token is expired
If you don’t want to use React Redux for this example, you can find the implementation at:
React JWT Authentication (without Redux) example
Happy learning, see you again!
If you want to use React Hooks for this example, you can find the implementation at:
– React Redux Login, Logout, Registration example with Hooks
– React Hooks: JWT Authentication (without Redux) example
Or add refresh token:
React + Redux: Refresh Token with Axios and JWT example
Further Reading
- React Router Guide
- React Components
- 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.js + Node.js Express + MongoDB
– React + Django
Another way to implement Form Validation:
React Form Validation example with Formik and Yup
Source Code
You can find the complete source code for this example on Github.
thank you for this Redux Auth tutorial.
Hi i am getting error please help me
connectAdvanced.js:261 Uncaught Error: Could not find “store” in the context of “Connect(App)”. Either wrap the root component in a , or pass a custom React context provider to and the corresponding React context consumer to Connect(App) in connect options.
Thanks for finally writing about React Redux Authentication example with Axios. Loved it!
I think this is one of the most important tutorial for me. And I’m glad reading your other tutorials. Good job, cheers
Dear bezkoder, thank you for impressive work, here and throughout the whole blog.
Question(to you and/or readers): Does this applicstion follow the MVC pattern as i understand?
It is still an SPA right?
(always having touble to trully understand the architecture/design pattern..! guess i need to get to get back to basics!) .
Gratefull for any help here…! Thanx. Keep it up!
Hi, i’m using this but with typescript, and things ‘s kinda complicated to me as a beginner. I try to do simple login, but in Login component, i use useDispacth and dispatch(Login(email,password)).then . but typescipt say property “then” does not exist on type ‘(dispatch: any) => Promise’. Can you help me
Thank you. It’s really helpful!!
Hi, thanks for your articles, it really helped me
Hello, Thanks for the article, that is great work. Could you, please chat me in private for a discussion? Very important
Excellent guide and it works like a charm! The only thing that I changed to my liking is the async-await syntax wrapped in try/catch in instead of that callback chain in action creators.
I will be going back to this tutorial whenever a similar flow is needed in any of my apps.
Thank you @bezkoder
How do i pass ROLES during SignUp?
Hi, you can add an input field to the signup form, then send it in the HTTP request body with
role
/roles
array.Hi, after filling the sign up form, it shows network error. what shall i do now?
Hi, did you run any backend for server?
how to run backend for server
Man you are those people with god level explanation, I was struggling with implementation in React at the auth states, i love you.