Authentication in a MERN app (MongoDB, Express, React, Node.js) is handled using JWT (JSON Web Tokens) for stateless authentication and a secure login/register flow between frontend and backend.
Let’s go step by step 👇
1. User Registration (Backend - Node.js + Express)
When a user signs up, hash their password before saving it to MongoDB.
import express from "express";
import bcrypt from "bcryptjs";
import jwt from "jsonwebtoken";
import User from "./models/User.js";
const router = express.Router();
router.post("/register", async (req, res) => {
try {
const { name, email, password } = req.body;
const hashed = await bcrypt.hash(password, 10);
const newUser = new User({ name, email, password: hashed });
await newUser.save();
res.status(201).json({ message: "User registered successfully" });
} catch (err) {
res.status(500).json({ message: err.message });
}
});
2. User Login (Backend)
Verify credentials and issue a JWT token on successful login.
router.post("/login", async (req, res) => {
try {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user) return res.status(404).json({ message: "User not found" });
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) return res.status(400).json({ message: "Invalid credentials" });
const token = jwt.sign({ id: user._id, role: user.role }, "secretKey", { expiresIn: "1h" });
res.json({ token, user: { id: user._id, name: user.name, email: user.email } });
} catch (err) {
res.status(500).json({ message: err.message });
}
});
3. Protect Routes (Backend Middleware)
Use a middleware to verify JWT on protected routes.
import jwt from "jsonwebtoken";
function auth(req, res, next) {
const token = req.header("Authorization")?.split(" ")[1];
if (!token) return res.status(401).json({ message: "No token provided" });
try {
const decoded = jwt.verify(token, "secretKey");
req.user = decoded;
next();
} catch {
res.status(401).json({ message: "Invalid token" });
}
}
Example protected route:
app.get("/api/profile", auth, (req, res) => {
res.json({ message: `Welcome user ${req.user.id}` });
});
4. Frontend (React)
Store token in localStorage or HTTP-only cookies (more secure).
Use it in requests to access protected routes.
// Login Request
const loginUser = async (email, password) => {
const res = await fetch("http://localhost:5000/api/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password }),
});
const data = await res.json();
localStorage.setItem("token", data.token); // store token
};
// Accessing Protected Data
const getProfile = async () => {
const token = localStorage.getItem("token");
const res = await fetch("http://localhost:5000/api/profile", {
headers: { Authorization: `Bearer ${token}` },
});
const data = await res.json();
console.log(data);
};
5. Logout
const logout = () => {
localStorage.removeItem("token");
navigate("/login");
};
6. Role-Based Access (Optional)
Include roles (admin, user, etc.) in the JWT payload and check them in middleware or frontend routes.
if (req.user.role !== "admin") return res.status(403).json({ message: "Access denied" });
✅ Summary
| Step | Description |
|---|---|
| 1 | User registers → password hashed (bcrypt) |
| 2 | User logs in → server verifies credentials |
| 3 | Server generates JWT → sends to client |
| 4 | Client stores token → attaches to requests |
| 5 | Middleware validates JWT before protected routes |
| 6 | Optional: Role-based access control |
Security Tips:
- Prefer HTTP-only cookies for storing tokens (prevents XSS).
- Always validate tokens server-side.
- Use HTTPS in production.