Part 1 --- JWT Authentication Flow (Understanding the Backend)
JSON Web Tokens are the modern standard for securing stateless APIs, particularly in MERN stacks where the frontend and backend are decoupled. The process starts when a user submits their credentials to a login endpoint; the Express server validates them, creates a signature using a secret key and the user's payload, and returns this signature as a long encoded string.
The frontend application persists this token, typically inside localStorage or sessionStorage, allowing the user's session to survive even after closing the browser tab. For every protected request, the frontend reads this token and attaches it as a Bearer token in the HTTP headers. Upon receiving it, the backend decodes and verifies the signature, extracting the user's ID and permissions without needing to query the database for every request, making authentication both secure and incredibly fast.
Example Backend Routes (Express)
While this is a frontend lesson, understanding the backend contract is essential. Here's a simplified Express setup for context:
js
// server/server.js (Express)
const jwt = require("jsonwebtoken");
const bcrypt = require("bcryptjs");
const User = require("./models/User");
app.post("/api/auth/register", async (req, res) => {
const { email, password } = req.body;
const hashedPassword = await bcrypt.hash(password, 10);
const user = new User({ email, password: hashedPassword });
await user.save();
res.status(201).json({ message: "User created" });
});
app.post("/api/auth/login", async (req, res) => {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user || !(await bcrypt.compare(password, user.password))) {
return res.status(401).json({ message: "Invalid credentials" });
}
// Sign a JWT with the user's ID and email, expiring in 1 hour
const token = jwt.sign(
{ id: user._id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn: "1h" }
);
res.json({ token, user: { id: user._id, email: user.email } });
});
Frontend API Service Functions
We'll create a dedicated service file that uses our Axios instance to interact with these endpoints.
jsx
// services/authService.js
import axiosInstance from "../utils/axiosInstance";
export const register = async (email, password) => {
const response = await axiosInstance.post("/auth/register", { email, password });
return response.data;
};
export const login = async (email, password) => {
const response = await axiosInstance.post("/auth/login", { email, password });
// Store the token and user data immediately upon success
const { token, user } = response.data;
localStorage.setItem("accessToken", token);
localStorage.setItem("user", JSON.stringify(user));
return response.data;
};
export const getProfile = async () => {
const response = await axiosInstance.get("/auth/profile"); // protected route
return response.data;
};