Axios, JWT, useContext, and User Authentication


Summary:
Axios is a promise-based HTTP client that simplifies API calls with features like automatic JSON parsing, request cancellation, and better error handling compared to fetch. It can be used globally in React by creating an Axios instance. JSON Web Token (JWT) is used for authentication, providing a stateless, secure, and fast method to verify user identity. The article demonstrates setting up JWT-based authentication in a Node.js + Express backend with MongoDB, and integrating it into a React frontend. It also covers using React's useContext hook for managing authentication state across components without prop drilling.
What is Axios?
Axios is a promise-based HTTP client for making requests from browsers (Frontend) and Node.js. It simplifies API calls and offers features like request/response interception, automatic JSON parsing, and timeout handling.
Why Use Axios Instead of fetch
?
Feature | fetch | axios |
Automatic JSON parsing | ❌ No (must call .json() ) | ✅ Yes |
Request cancellation | ❌ No | ✅ Yes |
Timeout handling | ❌ No | ✅ Yes |
Interceptors (pre/post-processing) | ❌ No | ✅ Yes |
Default headers | ❌ No | ✅ Yes |
Better error handling | ❌ No (need manual checking) | ✅ Yes |
How to Use Axios Globally in React
Instead of importing Axios in every component, define an Axios instance in a single file and reuse it.
Step 1: Install Axios
npm install axios
Step 2: Create an Axios Instance
Create a file axiosInstance.js
inside the src
folder:
import axios from "axios";
const api = axios.create({
baseURL: "http://localhost:3000", // Backend URL
headers: {
"Content-Type": "application/json",
},
});
// Request Interceptor (Add token automatically)
api.interceptors.request.use((config) => {
const token = localStorage.getItem("token");
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
export default api;
Step 3: Use Axios Instance in Any Component
import api from "./axiosInstance"; // Import our custom axios instance
...
const res = await api.post("/route", { email, password });
What is JWT (JSON Web Token)?
JWT (JSON Web Token) is like a digital pass given to a user after login. It proves their identity and allows them to access protected resources without logging in again.
Why Do We Use JWT?
Authentication → Confirms the user’s identity.
Stateless → No need to store sessions in the database.
Secure → Uses encryption & signatures to prevent tampering.
Fast → Since it’s just a token, no extra DB calls are needed after login.
How JWT Works for Login & Logout?
✅ Login Process
User enters email & password.
Server verifies credentials.
Server creates a JWT (token) with user info & expiration time.
Server sends the token to the user.
User stores the token (e.g., in
localStorage
).
✅ Accessing Protected Routes
User sends JWT in request headers (like an ID card).
Server verifies the token.
If valid → user gets access. If not → access is denied.
✅ Logout Process
Simply delete the token from
localStorage
orcookies
.Since the server doesn’t store sessions, the user is logged out.
JWT in Action
1️⃣ Generating JWT on Login (Backend)
const jwt = require("jsonwebtoken");
// Generate JWT Token
const token = jwt.sign({ id: user._id }, "secretKey", { expiresIn: "1h" });
res.json({ token });
2️⃣Storing & Using JWT in Frontend
localStorage.setItem("token", token);
const token = localStorage.getItem("token");
api.get("/dashboard", { headers: { Authorization: `Bearer ${token}` } });
user authentication (Registration, Login, Logout)
Set Up Backend (Node.js + Express + MongoDB)
1️⃣ Install Dependencies
npm install bcryptjs jsonwebtoken cors
2️⃣ Create index.js
const express = require("express");
const app = express();
require("dotenv").config();
app.use(cors({
origin: "http://localhost:5173", // Allow requests from your frontend
credentials: true, // Allow cookies and authentication headers
}));
const authRoutes = require('./Routes/authRoutes')
const connectDB = require("./db");
connectDB(); //database connected refer previous blog
app.use(express.json())
app.use(cors());
app.use('/api/auth', authRoutes)
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
Create Authentication Routes
Inside routes/authRoutes.js
:
const express = require("express");
const { register, login } = require("../Controllers/authController");
const router = express.Router();
router.post("/register", register);
router.post("/login", login);
module.exports = router;
Write Functions for login, register in Controller/authController.js
const express = require("express");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const User = require("../models/userModel");
const SECRET_KEY = process.env.JWT_SECRET || "secret";
//register
const register = async (req, res) => {
const { name, email, username, password } = req.body;
const userExists = await User.findOne({ email });
if (userExists) return res.status(400).json({ message: "Email already registered" });
const usernameExists = await User.findOne({username});
if(usernameExists) return res.status(400).json({message: "Username not available"});
const hashedPassword = await bcrypt.hash(password, 10);
const newUser = new User({ name, email, username, password: hashedPassword });
await newUser.save();
// Generate JWT Token
const token = jwt.sign({ id: newUser._id, username: newUser.username }, process.env.JWT_SECRET, {
expiresIn: "1d", // Token expires in 1 day
});
res.json({
message: "User registered successfully",
token,
user: { id: newUser._id, name: newUser.name, username: newUser.username } // Fix here
});
};
//login
const login = async (req, res) => {
console.log("login request");
const { username, password } = req.body;
const user = await User.findOne({ username });
if (!user) return res.status(400).json({ message: "Invalid username" });
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) return res.status(400).json({ message: "Invalid password" });
const token = jwt.sign({ id: user._id }, SECRET_KEY, { expiresIn: "1h" });
res.json({
token,
user: { id: user._id, name: user.name, username: user.username }
});
console.log("Login Response:", { token, user });
};
module.exports = { register, login };
Set Up Frontend
Create Axios Instance for Frontend in src/axios.js
import axios from "axios";
const api = axios.create({
baseURL: "http://localhost:3000/api",
headers: { "Content-Type": "application/json" },
});
export default api;
Register Component (src/pages/Register.js
)
import React from "react";
import styles from "./Register.module.css";
import { useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import api from "../../axios";
export default function Register() {
const [formData, setFormData] = useState({
email: "",
name: "",
username: "",
password: "",
});
const [err, setErr] = useState(null);
const handleChange = (event) => {
setFormData((prev) => ({
...prev,
[event.target.name]: event.target.value,
}));
};
const navigate = useNavigate();
const handleSubmit = async (event) => {
event.preventDefault();
try {
await api.post("/auth/register", formData);
localStorage.setItem("token", res.data.token);
alert("Registration successful!");
navigate("/");
} catch (err) {
setErr(err.response?.data?.message || "Registration failed");
}
};
return (
<div className={styles.register}>
<h1>Register</h1>
<form action="">
<input
type="email"
placeholder="Email"
name="email"
onChange={handleChange}
required
/>
<input
type="text"
placeholder="Name"
name="name"
onChange={handleChange}
required
/>
<input
type="text"
placeholder="Username"
name="username"
onChange={handleChange}
required
/>
<input
type="password"
placeholder="Password"
name="password"
onChange={handleChange}
required
/>
{err && err}
<button onClick={handleSubmit}>Sign Up</button>
</form>
</div>
);
}
Login Component
import React from "react";
import { useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import styles from "./Login.module.css";
import api from "../../axios";
function Login() {
const [formData, setFormData] = useState({
username: "",
password: "",
});
const [err, setErr] = useState(null);
const handleChange = (event) => {
setFormData((prev) => ({
...prev,
[event.target.name]: event.target.value,
}));
};
const navigate = useNavigate();
const handleLogin = async (e) => {
e.preventDefault();
try {
const res = await api.post("/login", formData);
localStorage.setItem("token", res.data.token);
alert("Login successful!");
navigate("/");
} catch (err) {
setErr(err.response?.data?.message || "Login failed");
}
};
return (
<div className={styles.login}>
<h1>Login</h1>
<form onSubmit={handleLogin}>
<input
type="text"
placeholder="Username"
name="username"
onChange={handleChange}
required
/>
<input
type="password"
placeholder="Password"
name="password"
onChange={handleChange}
required
/>
{err && err}
<button type="submit">Login</button>
</form>
</div>
);
}
export default Login;
Logout Function
In any component where you need logout:
const handleLogout = () => {
localStorage.removeItem("token");
alert("Logged out successfully!");
};
<button onClick={handleLogout}>Logout</button>
What is useContext
Hook?
useContext
is a React Hook that allows you to share state across components without prop drilling. It helps manage global state, like authentication, in a clean way.
write authContext in src/context/AuthContext.jsx
import { createContext, useEffect, useState } from "react";
import api from "../axios";
export const AuthContext = createContext();
export const AuthContextProvider = ({ children }) => {
const [currUser, setCurrUser] = useState(
JSON.parse(localStorage.getItem("user")) || null
);
const login = async(formData) => {
const res = await api.post('/auth/login', formData, {
withCredentials: true,
});
// Store only user details
setCurrUser(res.data.user);
// Store token in localStorage (optional if using httpOnly cookies)
localStorage.setItem("token", res.data.token);
};
useEffect(() => {
if (currUser) {
localStorage.setItem("user", JSON.stringify(currUser));
}
}, [currUser]);
return (
<AuthContext.Provider value={{ currUser, login }}>
{children}
</AuthContext.Provider>
);
};
Update Login.jsx
import React, { useContext, useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import styles from "./Login.module.css";
import { AuthContext } from "../../context/AuthContext";
function Login() {
const {login} = useContext(AuthContext);
const [formData, setFormData] = useState({
username: "",
password: "",
});
const [err, setErr] = useState(null);
const handleLogin = async (e) => {
e.preventDefault();
try {
await login(formData); // Ensure login completes before proceeding
alert("Login successful!");
navigate("/");
} catch (err) {
setErr(err.response?.data?.message || "Login failed");
}
};
const navigate = useNavigate();
const handleLogin = async (e) => {
e.preventDefault();
try {
login(formData);
alert("Login successful!");
navigate("/");
} catch (err) {
setErr(err.res.data || "Login failed");
}
};
return (
<div className={styles.login}>
<h1>Login</h1>
<form action="">
<input
type="text"
placeholder="Username"
name="username"
onChange={handleChange}
required
/>
<input
type="password"
placeholder="Password"
name="password"
onChange={handleChange}
required
/>
{err && err}
<button onClick={handleLogin}>Login</button>
</form>
</div>
);
}
export default Login;
Use AuthContext in App.jsx
import { useContext } from "react";
import { AuthContext, AuthContextProvider } from "./context/AuthContext.jsx";
//render page only if logged in
const ProtectedRoute = ({ children }) => {
const {currUser} = useContext(AuthContext);
if (!currUser) {
return <Navigate to="/" />;
}
return children;
};
function App() {
return (
<AuthContextProvider>
<RouterProvider router={router} />
</AuthContextProvider>
);
}
export default App;
Subscribe to my newsletter
Read articles from Omkar Kasture directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Omkar Kasture
Omkar Kasture
MERN Stack Developer, Machine learning & Deep Learning