JWT Authentication
π Concept
JSON Web Tokens (JWT) are the most popular authentication mechanism for Node.js APIs. A JWT is a self-contained, signed token that carries user identity and claims.
JWT structure:
Header.Payload.Signature
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjF9.signature
- Header β Algorithm and token type (
{"alg": "HS256", "typ": "JWT"}) - Payload β Claims (user data, expiration, issued at)
- Signature β HMAC or RSA signature to verify integrity
Authentication flow:
1. Client sends credentials (email/password) β POST /api/auth/login
2. Server verifies credentials against database
3. Server creates JWT with user ID + role β signs with secret key
4. Server returns JWT to client
5. Client stores JWT (localStorage, httpOnly cookie)
6. Client sends JWT in Authorization header β Bearer <token>
7. Server middleware verifies JWT signature β extracts user β req.user
Access tokens vs. Refresh tokens:
| Feature | Access Token | Refresh Token |
|---|---|---|
| Purpose | Authorize API requests | Get new access tokens |
| Lifetime | Short (15minβ1hr) | Long (7β30 days) |
| Storage | Memory / httpOnly cookie | httpOnly cookie only |
| Revocation | Difficult (stateless) | Easy (database-backed) |
π Real-world analogy: A JWT is like a concert wristband. The entry guard (server) issues the wristband (token) after checking your ticket (credentials). Inside the venue, you just flash your wristband (send token). The wristband is hard to fake (signed) and expires at the end of the concert (TTL).
π» Code Example
1// JWT Authentication β Complete Implementation23const express = require("express");4const jwt = require("jsonwebtoken");5const bcrypt = require("bcryptjs");67const app = express();8app.use(express.json());910const JWT_SECRET = process.env.JWT_SECRET || "dev-secret-change-in-production";11const JWT_EXPIRES_IN = "15m"; // Access token: 15 minutes12const REFRESH_TOKEN_EXPIRES_IN = "7d"; // Refresh token: 7 days1314// Mock user database15const users = [];16const refreshTokens = new Set(); // In production, use Redis/DB1718// 1. Register19app.post("/api/auth/register", async (req, res) => {20 const { name, email, password } = req.body;2122 // Validation23 if (!email || !password || password.length < 8) {24 return res.status(400).json({ error: "Invalid email or password (min 8 chars)" });25 }2627 // Check duplicate28 if (users.find((u) => u.email === email)) {29 return res.status(409).json({ error: "Email already registered" });30 }3132 // Hash password33 const hashedPassword = await bcrypt.hash(password, 12);3435 const user = { id: users.length + 1, name, email, password: hashedPassword, role: "user" };36 users.push(user);3738 // Generate tokens39 const { accessToken, refreshToken } = generateTokens(user);40 refreshTokens.add(refreshToken);4142 res.status(201).json({43 user: { id: user.id, name: user.name, email: user.email, role: user.role },44 accessToken,45 refreshToken,46 });47});4849// 2. Login50app.post("/api/auth/login", async (req, res) => {51 const { email, password } = req.body;5253 const user = users.find((u) => u.email === email);54 if (!user) return res.status(401).json({ error: "Invalid credentials" });5556 const isMatch = await bcrypt.compare(password, user.password);57 if (!isMatch) return res.status(401).json({ error: "Invalid credentials" });5859 const { accessToken, refreshToken } = generateTokens(user);60 refreshTokens.add(refreshToken);6162 res.json({ accessToken, refreshToken });63});6465// 3. Refresh token66app.post("/api/auth/refresh", (req, res) => {67 const { refreshToken } = req.body;6869 if (!refreshToken || !refreshTokens.has(refreshToken)) {70 return res.status(401).json({ error: "Invalid refresh token" });71 }7273 try {74 const decoded = jwt.verify(refreshToken, JWT_SECRET);75 const user = users.find((u) => u.id === decoded.userId);7677 // Rotate refresh token78 refreshTokens.delete(refreshToken);79 const tokens = generateTokens(user);80 refreshTokens.add(tokens.refreshToken);8182 res.json(tokens);83 } catch {84 refreshTokens.delete(refreshToken);85 res.status(401).json({ error: "Expired refresh token" });86 }87});8889// 4. Logout90app.post("/api/auth/logout", authenticate, (req, res) => {91 const { refreshToken } = req.body;92 refreshTokens.delete(refreshToken);93 res.status(204).end();94});9596// Helper: Generate token pair97function generateTokens(user) {98 const payload = { userId: user.id, role: user.role };99100 const accessToken = jwt.sign(payload, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN });101 const refreshToken = jwt.sign(payload, JWT_SECRET, { expiresIn: REFRESH_TOKEN_EXPIRES_IN });102103 return { accessToken, refreshToken };104}105106// 5. Authentication middleware107function authenticate(req, res, next) {108 const authHeader = req.headers.authorization;109110 if (!authHeader || !authHeader.startsWith("Bearer ")) {111 return res.status(401).json({ error: "No token provided" });112 }113114 const token = authHeader.split(" ")[1];115116 try {117 const decoded = jwt.verify(token, JWT_SECRET);118 req.user = decoded;119 next();120 } catch (err) {121 if (err.name === "TokenExpiredError") {122 return res.status(401).json({ error: "Token expired", code: "TOKEN_EXPIRED" });123 }124 res.status(401).json({ error: "Invalid token" });125 }126}127128// 6. Authorization middleware129function authorize(...roles) {130 return (req, res, next) => {131 if (!roles.includes(req.user.role)) {132 return res.status(403).json({ error: "Insufficient permissions" });133 }134 next();135 };136}137138// 7. Protected routes139app.get("/api/profile", authenticate, (req, res) => {140 const user = users.find((u) => u.id === req.user.userId);141 res.json({ id: user.id, name: user.name, email: user.email, role: user.role });142});143144app.get("/api/admin", authenticate, authorize("admin"), (req, res) => {145 res.json({ message: "Admin access granted", users: users.length });146});147148app.listen(3000, () => console.log("Auth server on http://localhost:3000"));
ποΈ Practice Exercise
Exercises:
- Implement a complete auth system with register, login, refresh, and logout endpoints
- Add role-based authorization middleware that checks user roles from the JWT payload
- Implement refresh token rotation β issue a new refresh token on every refresh and invalidate the old one
- Store refresh tokens in Redis with TTL instead of an in-memory Set
- Add password reset functionality with time-limited, single-use tokens
- Implement rate limiting on auth endpoints (max 5 login attempts per minute per IP)
β οΈ Common Mistakes
Storing JWTs in localStorage β vulnerable to XSS attacks; use httpOnly cookies for web apps or keep in memory with refresh token flow
Using a weak or short JWT secret β secrets should be 256+ bit random strings; never hardcode 'secret' as the key
Not setting expiration on access tokens β tokens that never expire are permanent credentials if stolen; use short TTLs (15minβ1hr)
Putting sensitive data in JWT payload β JWTs are base64-encoded (NOT encrypted!); anyone can decode the payload; only include user ID and role
Not validating the token algorithm β accepting 'none' algorithm is a known JWT vulnerability; always specify the expected algorithm
πΌ Interview Questions
π€ Mock Interview
Mock interview is powered by AI for JWT Authentication. Login to unlock this feature.