9.1 Authentication vs Authorization
Authentication (AuthN)
"Bạn là ai?" — verify identity (login với username/password, biometric, ...)
Authorization (AuthZ)
"Bạn được phép làm gì?" — check permission (admin? owner of this resource?)
Status code phân biệt: 401 Unauthorized = AuthN fail (chưa login). 403 Forbidden = AuthZ fail (đã login nhưng không có quyền).
9.3 Session-based Authentication
Cách truyền thống — server lưu state.
Flow
- User login với username + password → server verify
- Server tạo session ID (random string), lưu vào DB/Redis:
session_id → user_id - Server gửi
Set-Cookie: session=<session_id>; HttpOnly; Secure - Mỗi request tiếp theo, browser gửi cookie
- Server đọc session_id → lookup DB → biết user là ai
- Logout: server xoá session khỏi DB → cookie cũ không còn valid
Đặc điểm
- Stateful: server lưu session → cần Redis/DB
- Logout dễ: xoá session record → cookie cũ không dùng được
- Scaling: cần shared session store giữa các server (sticky session hoặc Redis)
- Cookie tự gửi: browser lo, dev không phải code
9.4 JWT — JSON Web Token
JWT (RFC 7519) — token chứa thông tin user + chữ ký. Server không cần lookup DB — verify chữ ký là tin được.
Cấu trúc
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 . eyJzdWIiOiI0MiIsIm5hbWUiOiJBbGljZSIsImV4cCI6MTcwOX0 . dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
↑ ↑ ↑
Header (base64url JSON) Payload (base64url JSON) Signature
3 phần ngăn cách bởi .. Header và payload là JSON base64url-encoded — đọc được, KHÔNG mã hoá. Signature ngăn tampering.
// Header
{
"alg": "HS256",
"typ": "JWT"
}
// Payload (claims)
{
"sub": "42", // subject (user ID)
"name": "Alice",
"role": "admin",
"iat": 1700000000, // issued at
"exp": 1700003600 // expires at (1 hour later)
}
// Signature = HMACSHA256(base64(header) + "." + base64(payload), secret)
Verify
Server có cùng secret. Recompute signature từ header.payload + secret. Nếu match → token chưa bị sửa, claims là thật.
Algorithms
- HS256 (HMAC + SHA-256): symmetric — cùng secret cho ký và verify. Nhanh, đơn giản.
- RS256 (RSA + SHA-256): asymmetric — private key ký, public key verify. Tốt cho microservice (chỉ auth service có private key).
- ES256 (ECDSA): tương tự RS256 nhưng key nhỏ hơn, nhanh hơn.
Lưu ý
- Không bỏ secret nhỏ vào JWT (vì payload đọc được)
- Phải set
exp— JWT không tự revoke được, nếu không expire sẽ "sống mãi" - Verify alg — đừng accept
alg: none(lỗ hổng kinh điển)
Code Node.js
const jwt = require('jsonwebtoken');
// Sign
const token = jwt.sign(
{ sub: 42, name: 'Alice', role: 'admin' },
process.env.JWT_SECRET,
{ expiresIn: '1h', algorithm: 'HS256' }
);
// Verify
try {
const payload = jwt.verify(token, process.env.JWT_SECRET);
console.log(payload.sub);
} catch (err) {
if (err.name === 'TokenExpiredError') { /* refresh */ }
if (err.name === 'JsonWebTokenError') { /* invalid */ }
}
9.5 Session vs JWT — câu phỏng vấn kinh điển
| Tiêu chí | Session | JWT |
|---|---|---|
| State | Stateful (server lưu) | Stateless (server không cần lưu) |
| Lookup mỗi request | Có (DB/Redis) | Không (chỉ verify signature) |
| Logout | Dễ (xoá session) | Khó (token vẫn valid đến exp) |
| Revoke 1 user | Dễ | Cần blocklist hoặc đổi secret |
| Scale | Cần shared store | Không cần — verify cục bộ |
| Microservice | Phức tạp (chia sẻ session) | Tốt (verify với public key) |
| Mobile/native client | Cookie không tự gửi | Header Authorization dễ |
| Token size | Nhỏ (chỉ session ID) | Lớn hơn (chứa claims) |
Khi nào Session?
- Web app đơn giản, monolith
- Cần revoke ngay lập tức (banking, admin tool)
- Có sẵn Redis/DB
Khi nào JWT?
- Microservice — auth service ký, các service khác verify với public key
- Mobile app, SPA — không cookie
- Cross-domain auth (SSO partial)
- Stateless API — không muốn DB lookup mỗi request
JWT phổ biến nhưng có nhiều pitfall: revoke khó, lưu trữ nguy hiểm (XSS lấy được), size lớn, cần manage secret. Nhiều project dùng JWT chỉ vì "trendy" — thực ra session đủ tốt.
JWT lưu ở đâu?
- Cookie HttpOnly + Secure + SameSite: an toàn nhất, JS không lấy được, browser tự gửi. Nhưng vẫn có CSRF risk (giảm với SameSite).
- localStorage: tiện nhưng XSS lấy được → nguy hiểm. Tránh.
- In-memory: an toàn nhất nhưng mất khi reload trang.
9.6 Access Token + Refresh Token
JWT exp ngắn (15 phút) thì user phải login lại liên tục. Exp dài (30 ngày) thì nguy hiểm khi lộ. Giải pháp: 2 token.
- Access token (JWT): exp ngắn (15-60 phút), gửi mỗi API request
- Refresh token: exp dài (7-30 ngày), chỉ dùng để lấy access token mới. Lưu DB (có thể revoke).
Flow
- Login → server trả access (15m) + refresh (7d)
- App gọi API với access token
- Access expired → app dùng refresh token gọi /refresh → nhận access token mới
- Refresh expired hoặc revoked → user phải login lại
Refresh token rotation
Tăng security: mỗi lần dùng refresh token, server cấp refresh token MỚI và invalidate cái cũ. Nếu attacker steal refresh + dùng → user dùng → server thấy 2 lần dùng cùng refresh → biết bị compromise → revoke tất cả.
9.7 OAuth 2.0 — login bằng Google/Facebook
OAuth 2.0 (RFC 6749) — protocol cho user của 1 service (Google) cấp quyền cho 1 service khác (your app) mà không cần share password.
4 actor
- Resource Owner: user (bạn)
- Client: app muốn truy cập data (vd Spotify)
- Authorization Server: nơi user login (Google)
- Resource Server: API có data (Google Drive API)
Authorization Code Flow (recommended)
Tại sao 2 bước (auth code + token)?
Authorization code đi qua URL của user (qua browser) — không an toàn để gửi trực tiếp access_token. Code chỉ dùng 1 lần, đổi lấy token qua server-to-server (có client_secret) → an toàn hơn.
PKCE — cho mobile/SPA
Mobile/SPA không thể giữ client_secret bí mật (decompile được). PKCE (Proof Key for Code Exchange) thêm code_verifier:
- Client tạo random
code_verifier, hash thànhcode_challenge - Gửi
code_challengetrong auth request - Khi đổi code → token, gửi
code_verifiergốc → server verify hash khớp
Đảm bảo: chỉ client thật sự khởi tạo flow mới đổi được code → token.
Scope
Định danh quyền cụ thể: email, profile, https://www.googleapis.com/auth/calendar.readonly. User see và approve scope.
Other grant types (less used)
- Client Credentials: server-to-server, không có user
- Implicit: cho SPA (deprecated, dùng Auth Code + PKCE)
- Resource Owner Password: app trực tiếp lấy password (nguy hiểm, deprecated)
- Device Code: cho TV, console (vd login Netflix trên TV bằng cách quét QR trên phone)
9.8 OpenID Connect (OIDC) — auth trên OAuth
OAuth 2.0 là authorization (cấp quyền truy cập API) — không phải authentication. Nhưng nhiều người dùng OAuth làm "Login with Google" — về mặt kỹ thuật là sai.
OpenID Connect = layer authentication trên OAuth. Thêm id_token (JWT chứa user info) vào response.
// id_token là JWT decode được:
{
"iss": "https://accounts.google.com",
"sub": "12345",
"email": "alice@gmail.com",
"name": "Alice",
"picture": "https://...",
"iat": ...,
"exp": ...
}
"Login with Google" trên app web = OIDC. Nhận id_token → biết user là ai → tạo session/JWT của riêng app.
9.9 Common Auth Attacks
1. Token Theft — XSS
Nếu JWT lưu localStorage, attacker tiêm script đọc localStorage.token → gửi server attacker. Fix: cookie HttpOnly + CSP.
2. CSRF (Cross-Site Request Forgery)
Attacker tạo trang ác chứa <form action="https://bank.com/transfer" method="POST">. User đã login bank, click form → browser tự gửi cookie session bank → tiền bị chuyển. Fix: SameSite cookie, CSRF token. Sẽ học sâu chương 10.
3. Replay Attack
Attacker bắt được token, gửi lại sau. Fix: exp ngắn, nonce, IP binding.
4. JWT alg=none
Bug kinh điển: lib parse JWT mà chấp nhận alg: none (không signature). Attacker chỉnh payload → server tin. Fix: verify alg whitelist.
5. JWT key confusion
Bug: lib dùng RS256 (asymmetric) nhưng config secret string → attacker dùng public key làm HS256 secret để forge. Fix: hardcode alg expected.
6. Session fixation
Attacker dụ user login với session ID attacker biết trước → sau khi login attacker dùng cùng session ID. Fix: rotate session ID sau login.
7. Brute force / credential stuffing
Thử password phổ biến, hoặc dùng password leak từ site khác. Fix: rate limit, MFA, captcha, hash bcrypt/argon2.
Bài tập
Login vào 1 site bạn dùng. Mở DevTools → Application → Cookies. Liệt kê: domain, expires, HttpOnly, Secure, SameSite của các cookie session.
Nếu site dùng JWT, copy token, paste vào jwt.io decoder. Đọc payload. Có nhạy cảm không?
Mỗi case sau, dùng Session hay JWT? Tại sao? (a) Banking app — admin có thể disable user ngay; (b) Mobile game multiplayer; (c) Internal tool monolith Rails; (d) Microservice authn shared giữa 10 services; (e) Public API ai cũng dùng được key.
Đăng ký 1 OAuth app trên GitHub Developer Settings. Implement Authorization Code flow đầu cuối: redirect → callback → trao đổi code → gọi user API.
Thiết kế DB schema cho refresh token với rotation. Mỗi refresh token có: id, user_id, expires_at, replaced_by.
JWT stateless → khó logout. Đề xuất 3 cách giải quyết: (a) blacklist; (b) ngắn hạn + refresh; (c) đổi secret.
🧪 Quiz cuối chương
Câu 1. Cookie attribute HttpOnly để?
Đáp án: chống JS đọc. XSS không thể lấy session cookie nếu HttpOnly. Best practice cho session.
Câu 2. SameSite cookie attribute để?
Đáp án: chống CSRF. Default Lax từ Chrome 80+ → fix nhiều CSRF tự động.
Câu 3. Khác biệt chính Session vs JWT?
Đáp án: stateful vs stateless. Session cần DB lookup, JWT verify cục bộ.
Câu 4. JWT signature dùng để?
Đáp án: chống tamper. Payload đọc được (base64), signature đảm bảo không sửa.
Câu 5. JWT lưu ở đâu là an toàn nhất?
Đáp án: HttpOnly cookie. localStorage có XSS risk. URL leak vào log/referer.
Câu 6. Refresh token để làm gì?
Đáp án: lấy access mới. Access ngắn hạn (15m) → security; refresh dài hạn (7d) → UX. Refresh lưu DB → revoke được.
Câu 7. OAuth 2.0 Authorization Code flow tại sao 2 bước?
Đáp án: bảo mật. Code chỉ dùng 1 lần, đổi qua HTTPS server-to-server.
Câu 8. "Login with Google" thực ra là OAuth hay OIDC?
Đáp án: OIDC. OAuth là authorization, OIDC mới là authentication. Nhiều người gọi nhầm.
Tổng kết chương 9
- ✅ AuthN = ai bạn? AuthZ = bạn được làm gì? Status 401 vs 403
- ✅ Cookie attributes: HttpOnly (chống XSS), Secure (HTTPS), SameSite (chống CSRF)
- ✅ Session: stateful, server lưu session ID → user; logout dễ; cần shared store
- ✅ JWT: stateless, header.payload.signature; verify signature đủ; logout/revoke khó
- ✅ Session vs JWT: chọn theo nhu cầu (revoke, microservice, mobile)
- ✅ Access + Refresh token + rotation = balance security/UX
- ✅ OAuth 2.0: authorization (cấp quyền API). Authorization Code flow chuẩn; PKCE cho mobile/SPA
- ✅ OIDC = OAuth + id_token cho authentication ("Login with Google")
- ✅ Common attacks: XSS theft, CSRF, replay, JWT alg=none, key confusion, session fixation
- ✅ Best practice: cookie HttpOnly + Secure + SameSite + bcrypt/argon2 password + rate limit + MFA