Lưu access token trong response body, refresh token trong httpOnly cookie — phải validate alg header (reject alg:none) và implement rotation (invalidate old refresh token on each use).
- Full auth flow: POST /login → verify credentials →
jwt.sign({ sub: user.id, role: user.role }, secret, { expiresIn: '15m' })tạo access token +jwt.sign({ sub: user.id }, refreshSecret, { expiresIn: '7d' })tạo refresh token → trả access token trong response body, refresh token trong httpOnly cookie (Set-Cookie: refreshToken=...; HttpOnly; Secure; SameSite=Strict). - Token storage: httpOnly cookie bảo vệ khỏi XSS (JS không đọc được), nhưng cần CSRF protection; localStorage tiện hơn nhưng XSS đánh cắp được — httpOnly cookie là best practice cho web apps.
- Auth middleware:
const token = req.headers.authorization?.split(' ')[1]; const payload = jwt.verify(token, secret); req.user = payload. - Refresh token rotation: mỗi lần dùng refresh token thì issue cặp mới (access + refresh) và invalidate refresh token cũ — lưu refresh token hash trong DB để có thể revoke.
- POST /refresh nhận cookie → verify → trả access token mới.
- POST /logout xóa cookie và blacklist refresh token.
Pitfall: không validate alg trong header → attacker đổi sang alg: none; dùng { algorithms: ['HS256'] } trong verify options.