Click Below to subscribe

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.

https://github.com/ultimateakash/react-jwt 

Leave Your Comment