Eduzan / Mern Stack
User Authentication and Authorization
Authentication = who the user is (login)
Authorization = what the user can access (permissions/roles)
Below is a complete, course-style guide for implementing auth in a Node.js + Express + MongoDB (MERN backend), including hashing, JWT, middleware, RBAC, and cookies/sessions.
- User submits name/email/password
- Backend validates input
- Password is hashed
- User is stored in MongoDB
- Optionally: auto-login by issuing token/cookie
User schema (Mongoose)
import mongoose from "mongoose";
const userSchema = new mongoose.Schema(
{
name: { type: String, required: true, trim: true },
email: { type: String, required: true, unique: true, lowercase: true, trim: true },
passwordHash: { type: String, required: true },
role: { type: String, enum: ["user", "admin"], default: "user" }
},
{ timestamps: true }
);
export const User = mongoose.model("User", userSchema);
- User submits email/password
- Backend finds user by email
- Compare password with stored hash
- If valid → issue session or JWT
- Return success + user info (never return passwordHash)
Passwords must never be stored as plain text. Hashing protects users even if the database leaks.
Use bcrypt:
- slow hashing = harder to crack
- includes salting automatically
Install:
npm i bcrypt
Hash password during registration
import bcrypt from "bcrypt";
import { User } from "./models/User.js";
export async function register(req, res) {
const { name, email, password } = req.body;
if (!name || !email || !password || password.length < 8) {
return res.status(400).json({ success: false, error: "Invalid input" });
}
const existing = await User.findOne({ email });
if (existing) {
return res.status(409).json({ success: false, error: "Email already registered" });
}
const saltRounds = 12;
const passwordHash = await bcrypt.hash(password, saltRounds);
const user = await User.create({ name, email, passwordHash });
res.status(201).json({
success: true,
user: { id: user._id, name: user.name, email: user.email, role: user.role }
});
}
Compare password during login
export async function login(req, res) {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user) return res.status(401).json({ success: false, error: "Invalid credentials" });
const ok = await bcrypt.compare(password, user.passwordHash);
if (!ok) return res.status(401).json({ success: false, error: "Invalid credentials" });
res.json({ success: true, message: "Login ok" });
}
There are two common approaches:
- Backend issues a JWT
- Frontend sends it in
Authorization: Bearer <token> - Backend verifies token on protected routes
Install:
npm i jsonwebtoken
Create token
import jwt from "jsonwebtoken";
function signToken(user) {
return jwt.sign(
{ userId: user._id.toString(), role: user.role },
process.env.JWT_SECRET,
{ expiresIn: "1h" }
);
}
Auth middleware (JWT)
export function requireAuth(req, res, next) {
const header = req.headers.authorization;
if (!header?.startsWith("Bearer ")) {
return res.status(401).json({ success: false, error: "Missing token" });
}
const token = header.split(" ")[1];
try {
const payload = jwt.verify(token, process.env.JWT_SECRET);
req.user = payload; // { userId, role, iat, exp }
next();
} catch {
return res.status(401).json({ success: false, error: "Invalid/Expired token" });
}
}
Protect route
app.get("/api/profile", requireAuth, async (req, res) => {
res.json({ success: true, user: req.user });
});
RBAC means users have roles like:
useradminmanager
and routes/actions are allowed based on those roles.
Store role in DB
We already added role field in the User schema.
RBAC middleware
export function requireRole(...allowedRoles) {
return (req, res, next) => {
if (!req.user?.role || !allowedRoles.includes(req.user.role)) {
return res.status(403).json({ success: false, error: "Forbidden" });
}
next();
};
}
Admin-only route
app.delete("/api/admin/users/:id",
requireAuth,
requireRole("admin"),
async (req, res) => {
// delete user logic
res.json({ success: true });
}
);
- Never store plain passwords
- Use bcrypt with 10–12+ rounds
- Store secrets in
.env - Use short token expiry + refresh tokens for long sessions (advanced)
- Prefer HttpOnly cookies in production
- Add rate limiting for login endpoints
- Validate inputs (use zod/joi/express-validator)
- Always return generic auth errors (“Invalid credentials”)
- Registration/Login: validate → hash password → store user → authenticate
- Password storage: bcrypt hash in MongoDB (
passwordHash) - Route protection: middleware verifies JWT or session
- RBAC: restrict endpoints by user roles
- Session & cookies: HttpOnly cookies improve security; sessions store state on server
End of lesson.