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.
- Lưu ý: không validate
algtrong header → attacker đổi sangalg: none; dùng{ algorithms: ['HS256'] }trong verify options.
Store access token in response body, refresh token in httpOnly cookie — must validate alg header (reject alg:none) and 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' })creates an access token +jwt.sign({ sub: user.id }, refreshSecret, { expiresIn: '7d' })creates a refresh token → return access token in response body, refresh token in an httpOnly cookie (Set-Cookie: refreshToken=...; HttpOnly; Secure; SameSite=Strict). - Token storage: httpOnly cookies protect against XSS (JS cannot read them) but require CSRF protection; localStorage is more convenient but can be stolen via XSS — httpOnly cookie is the best practice for web apps.
- Auth middleware:
const token = req.headers.authorization?.split(' ')[1]; const payload = jwt.verify(token, secret); req.user = payload. - Refresh token rotation: each use of a refresh token issues a new pair (access + refresh) and invalidates the old refresh token — store the refresh token hash in the DB to enable revocation.
- POST /refresh reads the cookie → verifies → returns a new access token.
- POST /logout clears the cookie and blacklists the refresh token.
Pitfall: not validating alg in the header → attacker can change to alg: none; use { algorithms: ['HS256'] } in verify options.