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
  1. Header β€” Algorithm and token type ({"alg": "HS256", "typ": "JWT"})
  2. Payload β€” Claims (user data, expiration, issued at)
  3. 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

codeTap to expand β›Ά
1// JWT Authentication β€” Complete Implementation
2
3const express = require("express");
4const jwt = require("jsonwebtoken");
5const bcrypt = require("bcryptjs");
6
7const app = express();
8app.use(express.json());
9
10const JWT_SECRET = process.env.JWT_SECRET || "dev-secret-change-in-production";
11const JWT_EXPIRES_IN = "15m"; // Access token: 15 minutes
12const REFRESH_TOKEN_EXPIRES_IN = "7d"; // Refresh token: 7 days
13
14// Mock user database
15const users = [];
16const refreshTokens = new Set(); // In production, use Redis/DB
17
18// 1. Register
19app.post("/api/auth/register", async (req, res) => {
20 const { name, email, password } = req.body;
21
22 // Validation
23 if (!email || !password || password.length < 8) {
24 return res.status(400).json({ error: "Invalid email or password (min 8 chars)" });
25 }
26
27 // Check duplicate
28 if (users.find((u) => u.email === email)) {
29 return res.status(409).json({ error: "Email already registered" });
30 }
31
32 // Hash password
33 const hashedPassword = await bcrypt.hash(password, 12);
34
35 const user = { id: users.length + 1, name, email, password: hashedPassword, role: "user" };
36 users.push(user);
37
38 // Generate tokens
39 const { accessToken, refreshToken } = generateTokens(user);
40 refreshTokens.add(refreshToken);
41
42 res.status(201).json({
43 user: { id: user.id, name: user.name, email: user.email, role: user.role },
44 accessToken,
45 refreshToken,
46 });
47});
48
49// 2. Login
50app.post("/api/auth/login", async (req, res) => {
51 const { email, password } = req.body;
52
53 const user = users.find((u) => u.email === email);
54 if (!user) return res.status(401).json({ error: "Invalid credentials" });
55
56 const isMatch = await bcrypt.compare(password, user.password);
57 if (!isMatch) return res.status(401).json({ error: "Invalid credentials" });
58
59 const { accessToken, refreshToken } = generateTokens(user);
60 refreshTokens.add(refreshToken);
61
62 res.json({ accessToken, refreshToken });
63});
64
65// 3. Refresh token
66app.post("/api/auth/refresh", (req, res) => {
67 const { refreshToken } = req.body;
68
69 if (!refreshToken || !refreshTokens.has(refreshToken)) {
70 return res.status(401).json({ error: "Invalid refresh token" });
71 }
72
73 try {
74 const decoded = jwt.verify(refreshToken, JWT_SECRET);
75 const user = users.find((u) => u.id === decoded.userId);
76
77 // Rotate refresh token
78 refreshTokens.delete(refreshToken);
79 const tokens = generateTokens(user);
80 refreshTokens.add(tokens.refreshToken);
81
82 res.json(tokens);
83 } catch {
84 refreshTokens.delete(refreshToken);
85 res.status(401).json({ error: "Expired refresh token" });
86 }
87});
88
89// 4. Logout
90app.post("/api/auth/logout", authenticate, (req, res) => {
91 const { refreshToken } = req.body;
92 refreshTokens.delete(refreshToken);
93 res.status(204).end();
94});
95
96// Helper: Generate token pair
97function generateTokens(user) {
98 const payload = { userId: user.id, role: user.role };
99
100 const accessToken = jwt.sign(payload, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN });
101 const refreshToken = jwt.sign(payload, JWT_SECRET, { expiresIn: REFRESH_TOKEN_EXPIRES_IN });
102
103 return { accessToken, refreshToken };
104}
105
106// 5. Authentication middleware
107function authenticate(req, res, next) {
108 const authHeader = req.headers.authorization;
109
110 if (!authHeader || !authHeader.startsWith("Bearer ")) {
111 return res.status(401).json({ error: "No token provided" });
112 }
113
114 const token = authHeader.split(" ")[1];
115
116 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}
127
128// 6. Authorization middleware
129function 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}
137
138// 7. Protected routes
139app.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});
143
144app.get("/api/admin", authenticate, authorize("admin"), (req, res) => {
145 res.json({ message: "Admin access granted", users: users.length });
146});
147
148app.listen(3000, () => console.log("Auth server on http://localhost:3000"));

πŸ‹οΈ Practice Exercise

Exercises:

  1. Implement a complete auth system with register, login, refresh, and logout endpoints
  2. Add role-based authorization middleware that checks user roles from the JWT payload
  3. Implement refresh token rotation β€” issue a new refresh token on every refresh and invalidate the old one
  4. Store refresh tokens in Redis with TTL instead of an in-memory Set
  5. Add password reset functionality with time-limited, single-use tokens
  6. 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.