A Complete Guide to Auth in React with Axios, JWT and Context API
Introduction
In this guide, we will explore how to implement authentication in a React application using Axios for HTTP requests, JWT (JSON Web Tokens) for secure token-based authentication, and the Context API for state management. We will also cover how to handle token refresh using Axios interceptors.
Prerequisites
- Basic knowledge of React
- Familiarity with Axios for making HTTP requests
- Understanding of JWT and how it works
- React Router for navigation
Setting Up the Project
Consider you have the following project structure:
my-app/
├── src/
│ ├── components/
│ ├── context/
│ ├── pages/
│ ├── api/
│ ├── App.js
│ ├── index.js
│ └── ...
├── package.json
└── ...
Step 1: Install Dependencies
First, ensure you have the necessary dependencies installed:
npm install axios react-router-dom
npm install jwt-decode
Step 2: Create the api.jsx file
// src/api/api.jsx
import axios from "axios";
const api = axios.create({
baseURL: "http://localhost:5000/api", // Replace with your API base URL
});
const Login = async (username, password) => {
const res = await api.post("/login", {
username,
password,
});
const { refresh, access } = res.data;
return { refresh, access };
};
const Logout = async (refresh) => {
await api.post("/logout", { refresh });
};
export default api;
export { Login, Logout };
Step 3: Create the AuthContext
// src/context/AuthContext.jsx
import { createContext, useState, useEffect } from "react";
import jwtDecode from "jwt-decode";
import api, { Login, Logout } from "../api/api";
import { Outlet, useNavigate } from "react-router-dom";
const AuthContext = createContext();
const AuthProvider = () => {
const [user, setUser] = useState(() => {
const refresh = localStorage.getItem("refresh");
try {
const decode = jwtDecode(refresh);
if (decode.exp * 1000 < Date.now()) {
localStorage.removeItem("refresh");
return null;
}
return decode;
} catch (e) {
return null;
}
});
const navigate = useNavigate();
const logout = async () => {
try {
const refresh = localStorage.getItem("refresh");
await Logout(refresh);
localStorage.removeItem("refresh");
localStorage.removeItem("access");
setUser(null);
navigate("/login");
} catch (error) {
console.error("Logout failed:", error);
}
};
const login = async (username, password) => {
try {
const { refresh, access } = await Login(username, password);
localStorage.setItem("refresh", refresh);
localStorage.setItem("access", access);
setUser(jwtDecode(refresh));
navigate("/");
} catch (error) {
console.error("Login failed:", error);
}
};
useEffect(() => {
const requestInterceptor = api.interceptors.request.use(
(config) => {
const access = localStorage.getItem("access");
if (access) {
config.headers["Authorization"] = `Bearer ${access}`;
}
return config;
},
(error) => Promise.reject(error)
);
const responseInterceptor = api.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const refresh = localStorage.getItem("refresh");
const { data } = await api.post("/refresh", { refresh });
localStorage.setItem("access", data.access);
originalRequest.headers["Authorization"] = `Bearer ${data.access}`;
return api(originalRequest);
} catch (err) {
console.error("Token refresh failed:", err);
localStorage.removeItem("refresh");
localStorage.removeItem("access");
setUser(null);
navigate("/login");
}
}
return Promise.reject(error);
}
);
return () => {
api.interceptors.request.eject(requestInterceptor);
api.interceptors.response.eject(responseInterceptor);
};
}, []);
return (
<AuthContext.Provider value={{ user, login, logout }}>
<Outlet />
</AuthContext.Provider>
);
};
export default AuthContext;
export { AuthProvider };
Step 4: Create the Login Page
// src/pages/Login.jsx
import React, { useContext, useState } from "react";
import AuthContext from "../context/AuthContext";
const Login = () => {
const { login } = useContext(AuthContext);
const [formData, setFormData] = useState({ username: "", password: "" });
const handleChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const handleSubmit = (e) => {
e.preventDefault();
login(formData.username, formData.password);
};
return (
<div>
<h2>Login</h2>
<form onSubmit={handleSubmit}>
<input name="username" onChange={handleChange} value={formData.username} placeholder="Username" />
<input name="password" type="password" onChange={handleChange} value={formData.password} placeholder="Password" />
<button type="submit">Login</button>
</form>
</div>
);
};
export default Login;
Step 5: Create the Protected Route
// src/Routes/ProtectedRoute.jsx
import React, { useContext } from "react";
import { Navigate, Outlet } from "react-router-dom";
import AuthContext from "../context/AuthContext";
const ProtectedRoute = () => {
const { user } = useContext(AuthContext);
return user ? <Outlet /> : <Navigate to="/login" />;
};
export default ProtectedRoute;
Create PublicRoute.jsx
// src/Routes/PublicRoute.jsx
import React, { useContext } from "react";
import { Navigate, Outlet } from "react-router-dom";
import AuthContext from "../context/AuthContext";
const PublicRoute = () => {
const { user } = useContext(AuthContext);
return user ? <Navigate to="/" /> : <Outlet />;
};
export default PublicRoute;
Showing different pages according to the user
// src/Routes/Shared.jsx
import { useContext } from "react";
import AuthContext from "../context/AuthContext";
import { Library, Landing, User } from "../pages";
const Shared = () => {
const { user } = useContext(AuthContext);
if (!user) return <Landing />;
if (user.role === "admin") return <Library />;
return <User />;
};
export default Shared;
Step 6: Set Up the App Component
// src/App.js
import React from "react";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import { AuthProvider } from "./context/AuthContext";
import Login from "./pages/Login";
import ProtectedRoute from "./Routes/ProtectedRoute";
import PublicRoute from "./Routes/PublicRoute";
import Shared from "./Routes/Shared";
import UserProfile from "./pages/UserProfile";
import Register from "./pages/Register";
import ProtectLibrary from "./pages/ProtectLibrary";
import AddBook from "./pages/AddBook";
const router = createBrowserRouter([
{
element: <AuthProvider />,
children: [
{
element: <PublicRoute />,
children: [
{ path: "/login", element: <Login /> },
{ path: "/register", element: <Register /> },
],
},
{
element: <ProtectedRoute />,
children: [
{ path: "/profile", element: <UserProfile /> },
{
path: "/library",
element: <ProtectLibrary />,
children: [{ path: "add-book", element: <AddBook /> }],
},
],
},
{ path: "/", element: <Shared /> },
],
},
]);
const App = () => {
return <RouterProvider router={router} />;
};
export default App;