How to Implement JWT Authentication in React 2023
JSON Web Token (jwt) is an open standard that allows two parties to securely send data as JSON objects.
In this article, we will implement jwt authentication in react from scratch.
1. Let's install create-react-app package.
npm i -g create-react-app
2. Create a new react project.
create-react-app react-jwt
cd react-jwt
3. Create .env file root folder and add PORT environment variable.
PORT=3001
4. Start the application
npm start
5. Install bootstrap and import bootstrap.css in index.css
npm i bootstrap
src/index.css
@import "~bootstrap/dist/css/bootstrap.css";
Note:- I had already implemented node backend with JWT checkout the links below.
(a). JWT with Mongoose
(b). JWT with MySQL
Both above implementations have exact same APIs. so you can use anyone of them.
6. Clone the mongoose implementation.
git clone https://github.com/ultimateakash/node-mongoose-jwt.git
Install dependencies
cd node-mongoose-jwt
npm install
npm start
Now our backend server is running(http://localhost:3000). so we can use the following APIs in our react application.
base url: http://localhost:3000/api
API | Method | Endpoint |
Login User | POST | /login |
Register User | POST | /register |
Get Profile | GET | /user |
Logout User | GET | /logout |
7. Open .env and create REACT_APP_API_BASE_URL environment variable.
PORT=3001
REACT_APP_API_BASE_URL=http://localhost:3000/api
8. Install axios http client.
npm i axios
9. Create a http-client utility.
src/utils/http-client.js
import axios from 'axios';
import authService from '../services/auth.service';
const instance = axios.create({
baseURL: process.env.REACT_APP_API_BASE_URL
});
instance.interceptors.request.use((config) => {
const authUser = authService.getAuthUser();
if (authUser) {
config.headers['authorization'] = `Bearer ${authUser.access_token}`;
}
return config;
}, (error) => {
return Promise.reject(error);
});
instance.interceptors.response.use((response) => {
return response;
}, (error) => {
if (error?.response?.status === 401) {
localStorage.removeItem('authUser');
window.location.reload();
} else {
return Promise.reject(error.response);
}
});
const get = (url, params, config = {}) => instance.get(url, { params, ...config });
const post = (url, data, config = {}) => instance.post(url, data, config);
const methods = { get, post };
export default methods;
10. Create auth service.
src/services/auth.service.js
import http from "../utils/http-client";
const login = (data) => {
return http.post('/login', data, {
transformResponse: [(result) => {
const parsed = JSON.parse(result);
localStorage.setItem('authUser', JSON.stringify(parsed));
return parsed;
}]
});
}
const register = (data) => {
return http.post('/register', data);
}
const profile = () => {
return http.get('/user');
}
const logout = () => {
return http.get('/logout', null, {
transformResponse: [(result) => {
localStorage.removeItem('authUser');
return JSON.parse(result);
}]
});
}
const getAuthUser = () => {
return JSON.parse(localStorage.getItem('authUser'));
}
const methods = {
login,
register,
profile,
logout,
getAuthUser
}
export default methods;
11. Install react-router-dom npm package.
npm i react-router-dom
After completing the installation open index.js and import the BrowserRouter component and wrap it around the App component.
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter } from "react-router-dom";
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
12. Create auth guard.
src/guards/auth.guard.js
import React from 'react';
import { Navigate, Outlet } from 'react-router-dom';
import authService from '../services/auth.service';
const AuthGuard = () => {
const authUser = authService.getAuthUser();
return authUser ? <Outlet /> : <Navigate to={'/'} replace />
}
export default AuthGuard
13. Install react-hook-form npm package and its validation dependencies.
npm i react-hook-form yup @hookform/resolvers
14. Install react-hot-toast for notification.
react-hot-toast
15. Next we need to create a few components.
src/components/login.component.jsx
import React, { useState } from 'react';
import { yupResolver } from '@hookform/resolvers/yup';
import { useNavigate } from 'react-router-dom';
import { useForm } from 'react-hook-form';
import toast from 'react-hot-toast';
import * as Yup from 'yup';
import authService from '../services/auth.service';
const LoginComponent = () => {
const navigate = useNavigate();
const [isSubmitted, setIsSubmitted] = useState(false);
const schema = Yup.object().shape({
email: Yup.string().email().required(),
password: Yup.string().required()
});
const { register, handleSubmit, formState: { errors, isDirty, isValid } } = useForm({
mode: 'all',
resolver: yupResolver(schema)
});
const handleValidSubmit = async (data) => {
setIsSubmitted(true)
try {
const result = await authService.login(data);
if (result.data) {
navigate('/profile');
}
} catch (error) {
toast.error(error.data.message);
}
setIsSubmitted(false)
}
return (
<div className="row">
<div className="col-6 offset-3">
<form onSubmit={handleSubmit(handleValidSubmit)}>
<div className="mb-3">
<label htmlFor="inputEmail" className="form-label">Email address</label>
<input type="email" className="form-control" id="inputEmail" {...register('email')} />
<div className="form-text text-danger">
{errors.email && <p>{errors.email.message}</p>}
</div>
</div>
<div className="mb-3">
<label htmlFor="inputPassword" className="form-label">Password</label>
<input type="password" className="form-control" id="inputPassword" {...register('password')} />
<div className="form-text text-danger">
{errors.password && <p>{errors.password.message}</p>}
</div>
</div>
<button type="submit" disabled={isSubmitted || !isDirty || !isValid} className="btn btn-primary">Submit</button>
</form>
</div>
</div>
)
}
export default LoginComponent
src/components/register.component.jsx
import React from 'react';
import { yupResolver } from '@hookform/resolvers/yup';
import { useNavigate } from 'react-router-dom';
import { useForm } from 'react-hook-form';
import toast from 'react-hot-toast';
import * as Yup from 'yup';
import authService from '../services/auth.service';
import { useState } from 'react';
const RegisterComponent = () => {
const navigate = useNavigate();
const [isSubmitted, setIsSubmitted] = useState();
const schema = Yup.object().shape({
name: Yup.string().required(),
email: Yup.string().email().required(),
password: Yup.string().required()
.matches(/(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/, 'Password should contains a lowercase, a uppercase character and a digit.')
});
const { register, handleSubmit, formState: { errors, isDirty, isValid } } = useForm({
mode: 'all',
resolver: yupResolver(schema)
});
const handleValidSubmit = async (data) => {
setIsSubmitted(true)
try {
const result = await authService.register(data);
if (result.data) {
navigate('/');
}
} catch (error) {
toast.error(error.data.message);
}
setIsSubmitted(false)
}
return (
<div className="row">
<div className="col-6 offset-3">
<form onSubmit={handleSubmit(handleValidSubmit)}>
<div className="mb-3">
<label htmlFor="inputName" className="form-label">Name</label>
<input type="name" className="form-control" id="inputName" {...register('name')} />
<div className="form-text text-danger">
{errors.name && <p>{errors.name.message}</p>}
</div>
</div>
<div className="mb-3">
<label htmlFor="inputEmail" className="form-label">Email address</label>
<input type="email" className="form-control" id="inputEmail" {...register('email')} />
<div className="form-text text-danger">
{errors.email && <p>{errors.email.message}</p>}
</div>
</div>
<div className="mb-3">
<label htmlFor="inputPassword" className="form-label">Password</label>
<input type="password" className="form-control" id="inputPassword" {...register('password')} />
<div className="form-text text-danger">
{errors.password && <p>{errors.password.message}</p>}
</div>
</div>
<button type="submit" className="btn btn-primary" disabled={isSubmitted || !isDirty || !isValid}>Submit</button>
</form>
</div>
</div>
)
}
export default RegisterComponent
src/components/profile.component.jsx
import React, { useState, useEffect } from 'react';
import toast from 'react-hot-toast';
import authService from '../services/auth.service';
const ProfileComponent = () => {
const [user, setUser] = useState(null);
useEffect(() => {
fetchProfile();
}, []);
const fetchProfile = async () => {
try {
const result = await authService.profile();
setUser(result.data)
} catch (error) {
toast.error(error.data.message);
}
}
return (
<div className="row">
<div className="col-6 offset-3">
<ul className="list-group">
<li className="list-group-item"><span className="fw-bold">Id</span> - {user?._id}</li>
<li className="list-group-item"><span className="fw-bold">Name</span> - {user?.name}</li>
<li className="list-group-item"><span className="fw-bold">Email</span> - {user?.email}</li>
</ul>
</div>
</div>
)
}
export default ProfileComponent
src/components/nav-bar.component.jsx
import React from 'react';
import toast from 'react-hot-toast';
import { Link, NavLink, useNavigate } from 'react-router-dom';
import authService from '../services/auth.service';
const NavBarComponent = () => {
const navigate = useNavigate();
const authUser = authService.getAuthUser();
const getActiveClass = ({ isActive }) => isActive ? 'nav-link active' : 'nav-link';
const handleLogout = async () => {
try {
await authService.logout();
navigate('/');
} catch (error) {
toast.error(error.data.message);
}
}
return (
<header className="d-flex flex-wrap justify-content-center py-3 mb-4 border-bottom">
<div className="d-flex align-items-center mb-3 mb-md-0 me-md-auto text-dark text-decoration-none">
<span className="fs-4">React JWT</span>
</div>
<ul className="nav nav-pills">
{
authUser
?
<li className="nav-item">
<Link to={'#'} onClick={handleLogout} className="nav-link active">Logout</Link>
</li>
:
<>
<li className="nav-item">
<NavLink to={'/'} end className={getActiveClass}>Login</NavLink>
</li>
<li className="nav-item">
<NavLink to={'/register'} end className={getActiveClass}>Register</NavLink>
</li>
</>
}
</ul>
</header>
)
}
export default NavBarComponent
src/components/not-found.component.jsx
import React from 'react'
const NotFoundComponent = () => {
return (
<div>Page Not Found</div>
)
}
export default NotFoundComponent
16. Create app router.
src/app.router.js
import React from 'react';
import { Route, Routes } from 'react-router-dom';
import AuthGuard from './guards/auth.guard';
import LoginComponent from "./components/login.component";
import ProfileComponent from "./components/profile.component";
import RegisterComponent from "./components/register.component";
import NotFoundComponent from "./components/not-found.component";
const AppRouter = () => {
return (
<Routes>
<Route exact path='/' element={<LoginComponent />} />
<Route path='/register' element={<RegisterComponent />} />
<Route element={<AuthGuard />}>
<Route path='/profile' element={<ProfileComponent />} />
</Route>
<Route path='*' element={<NotFoundComponent />} />
</Routes>
)
}
export default AppRouter;
17. Open App.js and make the following changes.
import './App.css';
import AppRouter from './app.router';
import { Toaster } from 'react-hot-toast';
import NavBarComponent from './components/nav-bar.component';
function App() {
return (
<div className="container">
<NavBarComponent />
<AppRouter />
<Toaster position="top-right" />
</div>
);
}
export default App;
18. Finally test the application.
Checkout my full react-jwt example.
Leave Your Comment