Axios, JWT, useContext, and User Authentication

Omkar KastureOmkar Kasture
8 min read

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?

Featurefetchaxios
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

  1. User enters email & password.

  2. Server verifies credentials.

  3. Server creates a JWT (token) with user info & expiration time.

  4. Server sends the token to the user.

  5. User stores the token (e.g., in localStorage).

Accessing Protected Routes

  1. User sends JWT in request headers (like an ID card).

  2. Server verifies the token.

  3. If valid → user gets access. If not → access is denied.

Logout Process

  • Simply delete the token from localStorage or cookies.

  • 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;

1
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