Mục lục
Mục Tiêu Bài Học
Sau bài học, bạn sẽ:
- Phân biệt JWT (JSON Web Token) vs Session cookie — trade-off cốt lõi giữa stateless và stateful.
- Hiểu JWT 3-part structure:
header.payload.signaturebase64url-encode dot-separated. - Phân biệt HS256 (HMAC-SHA256 symmetric) vs RS256 (RSA-SHA256 asymmetric).
- Hiểu claims standard RFC 7519 (
iss+sub+exp+iat+jti) và custom claims project-specific. - Cài
jsonwebtokencrate v9 vào workspace + skeleton moduleshop-common::jwt. - Design Shop API auth flow: register → login → refresh → logout → revoke với token strategy lock.
- Roadmap Group 9 (B86-B95) — 10 bài auth production-grade từ login đến MFA + OAuth2.
JWT Vs Session Cookie — Trade-Off Cốt Lõi
Auth REST API có 2 pattern chính cho việc giữ identity giữa các request: Session cookie server-side state vs JWT self-contained token. Cả 2 đều giải được bài toán "user đã đăng nhập là ai", nhưng đánh đổi khác hẳn nhau.
Session cookie (stateful server-side):
- Server sinh session ID random (vd
sess_abc123), lưu vào store (Redis/DB) cùng với data user (user_id, role, expires_at). - Client lưu session ID trong cookie HttpOnly, gửi kèm mỗi request.
- Server đọc session ID → query store → load data user → trust.
- Pros: revoke instant (chỉ cần delete session khỏi store), cookie payload nhỏ (chỉ ID), data user có thể update real-time (đổi role, đổi quota).
- Cons: server stateful (mọi instance API phải share session store), scale horizontal khó (cần Redis cluster + sticky session hoặc shared cache), latency thêm 1 round-trip Redis mỗi request.
JWT (stateless self-contained):
- Server encode claims (user_id, role, expires_at) vào token tự chứa data, sign bằng secret/private key.
- Client lưu token, gửi qua
Authorization: Bearer <token>header. - Server verify signature → trust claims trong payload → KHÔNG cần query store.
- Pros: stateless (mọi instance verify được mà không cần shared store), scale horizontal dễ (chỉ cần share secret), microservice friendly (service A issue token, service B verify được), zero latency network cho verify.
- Cons: revoke KHÔNG instant (token valid tới khi
exphết hạn — kể cả khi đã đổi password), payload size lớn (200-500 bytes vs 30 bytes session ID), data user trong token stale tới khi refresh (đổi role không reflect ngay).
Shop API lock chọn JWT vĩnh viễn (B11 lock "REST stateless" continued). Mitigate điểm yếu revoke qua 2 tầng:
- Short-lived access token — JWT chỉ valid 15 phút, hạn chế cửa sổ tấn công nếu token bị leak.
- Refresh token rotation — refresh token long-lived (30 ngày) opaque (KHÔNG JWT) lưu DB, mỗi lần dùng cấp mới + revoke cũ, attacker steal token cũ dùng không được.
- Blocklist Redis G15 (preview) — revoke instant qua
jtitrong blocklist với TTL = token expiry.
JWT 3-Part Structure
JWT (RFC 7519) là chuỗi 3 phần ngăn cách bằng dấu chấm: <header>.<payload>.<signature>. Mỗi phần là dữ liệu binary base64url-encode (variant url-safe của base64, không có +///=).
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZXhwIjoxNzE4NjAwMDAwfQ.signature_bytes_base64url
Header (phần 1) là JSON metadata về cách verify token, encode base64url:
{
"alg": "HS256",
"typ": "JWT"
}
2 trường tối thiểu: alg (thuật toán sign) + typ (loại token, mặc định JWT). Optional kid (key ID) cho rotation, cty (content type) khi token bọc trong JWE.
Payload (phần 2) là JSON chứa claims — các thông tin về subject (thường là user). Cũng encode base64url:
{
"iss": "shop.blogcode.vn",
"sub": "1",
"exp": 1718600000,
"iat": 1718596400,
"jti": "550e8400-e29b-41d4-a716-446655440000",
"role": "customer",
"email_verified": true,
"token_type": "access"
}
Signature (phần 3) là HMAC hoặc chữ ký RSA của chuỗi base64url(header) + "." + base64url(payload) tính bằng secret (HS256) hoặc private key (RS256). Verify lại y hệt: server tính lại signature từ header + payload, so sánh với signature trong token — match thì trust.
Điểm quan trọng nhất: payload KHÔNG encrypt, chỉ encode base64url. Ai có token cũng decode được payload bằng base64 -d hoặc jwt.io. Signature chỉ đảm bảo integrity (chống tamper — sửa payload thì signature không match nữa), KHÔNG đảm bảo confidentiality (giữ bí mật).
Hệ quả lock vĩnh viễn cho Shop API:
- KHÔNG bao giờ lưu password (hash hay plain) trong claims.
- KHÔNG bao giờ lưu secret/API key trong claims.
- KHÔNG bao giờ lưu PII nhạy cảm (số CMND, số thẻ tín dụng, địa chỉ chi tiết) trong claims.
- Nếu cần confidentiality, dùng JWE (JSON Web Encryption RFC 7516) thay JWT — Shop API KHÔNG dùng vì JWT đủ cho use case auth.
HS256 Vs RS256 — Symmetric Vs Asymmetric
JWT hỗ trợ nhiều thuật toán sign (alg). 2 thuật toán phổ biến nhất là HS256 (HMAC-SHA256) và RS256 (RSA-SHA256). Khác biệt chính là symmetric vs asymmetric key.
HS256 (HMAC-SHA256) — symmetric:
- 1 secret key duy nhất (≥ 32 bytes random) dùng cho cả sign và verify.
- Pros: nhanh (~10x RS256), key nhỏ, config đơn giản, dùng
EncodingKey::from_secret(&secret)+DecodingKey::from_secret(&secret)cùng 1 secret. - Cons: mọi service verify token phải có secret → single trust boundary. Share secret với third-party = third-party cũng sign được token giả.
RS256 (RSA-SHA256) — asymmetric:
- Private key để sign, public key để verify (cặp RSA 2048-bit trở lên).
- Pros: third-party verify được với public key (không sign được), key rotation dễ qua JWKS endpoint (
/.well-known/jwks.json), OIDC standard, OAuth2 ecosystem support. - Cons: chậm hơn HS256 (~10x), key lớn hơn (2KB vs 32 bytes), config phức tạp (PEM/DER format, key management).
Bảng decision matrix:
Scenario | HS256 | RS256
-------------------------------------+----------------+----------------
Monolith API 1 service | OK | overkill
Microservice cùng team (trust shared)| OK | OK
Third-party verify (mobile SDK, | KHÔNG | OK
partner API, public consumer) | |
OIDC compliance (Google, Auth0, | KHÔNG | OK
Keycloak, Okta) | |
Key rotation | khó | dễ (JWKS)
Performance (sign + verify) | nhanh (~10us) | chậm (~100us)
Key size | 32+ bytes | 2048-bit RSA
Crypto library complexity | đơn giản | phức tạp
Shop API lock chọn HS256 cho MVP: monolith API duy nhất 1 trust boundary, không có third-party verify, không OIDC compliance yêu cầu. Preview RS256 G18 deploy nếu cần share auth với mobile SDK hoặc cấp token cho partner API. Migrate HS256 → RS256 sau đó chỉ cần đổi key + thuật toán trong Header::new(Algorithm::RS256), claims giữ nguyên — kiến trúc backward compatible.
Claims Standard Vs Custom
RFC 7519 định nghĩa 7 standard claims (registered claims) tên 3 chữ ngắn cho hiệu quả wire size:
iss(issuer) — bên phát hành token, vd"shop.blogcode.vn". Verify giúp chống token từ issuer khác trộn vào.sub(subject) — đối tượng của token, Shop API dùnguser.iddạng string ("1").exp(expiration) — Unix timestamp seconds, token hết hạn sau timestamp này. MANDATORY cho Shop API.iat(issued at) — Unix timestamp seconds lúc phát hành.nbf(not before) — timestamp token bắt đầu valid (hiếm dùng, thường =iat).jti(JWT ID) — ID unique của token (UUID v4), dùng cho blocklist revoke.aud(audience) — token này dành cho ai, vd["shop-api", "admin-dashboard"]. Hiếm dùng cho single API.
Custom claims Shop API (đặt cùng level với standard, tránh trùng tên):
role:"customer"/"admin"— cho RBAC Group 10.email_verified:bool— block endpoint nhạy cảm nếu chưa verify email (lock B70 lifecycle).token_type:"access"/"refresh"— phân biệt token loại nào (defense-in-depth).
Cấu trúc claims chuẩn Shop API lock vĩnh viễn:
{
"iss": "shop.blogcode.vn",
"sub": "1",
"exp": 1718600000,
"iat": 1718596400,
"jti": "550e8400-e29b-41d4-a716-446655440000",
"role": "customer",
"email_verified": true,
"token_type": "access"
}
Lock rule vĩnh viễn cho mọi claim Shop API:
subMANDATORY (user_id) — không có thì middleware extractor reject 401.expMANDATORY — KHÔNG token vĩnh viễn, mọi token đều có deadline.jtiMANDATORY — cho blocklist Redis G15 revoke instant theo jti.issMANDATORY — verify"shop.blogcode.vn"chống cross-issuer attack.- KHÔNG bao giờ thêm:
password_hash,payment_payload,credit_card,cmnd,address_full,api_key,secret. - Code review reject PR thêm claim sensitive (lint custom G18 grep regex cảnh báo dev compile-time).
Cài jsonwebtoken Crate + Helper Module
jsonwebtoken là crate Rust phổ biến nhất cho JWT, support 9 thuật toán (HS256/384/512, RS256/384/512, PS256/384/512, ES256/384, EdDSA), API đơn giản qua encode/decode generic, validate claims tự động qua Validation struct.
Add workspace dep ở root Cargo.toml:
# File: shop/Cargo.toml (workspace root, extend B10)
[workspace.dependencies]
# ... 19 dep đã có (axum, tokio, serde, sqlx, ...)
jsonwebtoken = "9"
Consume ở shop-common (vì JWT là cross-cutting concern, dùng ở handler + middleware + service):
# File: crates/shop-common/Cargo.toml
[dependencies]
# ... dep đã có
jsonwebtoken = { workspace = true }
Tạo module mới shop-common::jwt với skeleton (B87 implement đầy đủ login endpoint + sinh token):
// File: crates/shop-common/src/jwt.rs (NEW B86 skeleton)
use serde::{Serialize, Deserialize};
use jsonwebtoken::{encode, decode, Header, Validation, EncodingKey, DecodingKey, Algorithm};
use crate::error::AppError;
/// Claims chuẩn Shop API — lock B86 vĩnh viễn.
/// 5 standard (iss + sub + exp + iat + jti) + 3 custom (role + email_verified + token_type).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Claims {
pub iss: String, // "shop.blogcode.vn"
pub sub: String, // user_id as string (RFC 7519 §4.1.2)
pub exp: i64, // Unix timestamp seconds expiration
pub iat: i64, // Unix timestamp seconds issued at
pub jti: String, // UUID v4 — cho blocklist revoke G15
pub role: String, // "customer" / "admin" — RBAC G10
pub email_verified: bool, // block endpoint nếu chưa verify B70
pub token_type: TokenType,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum TokenType {
Access,
Refresh,
}
JwtService struct gói encoding key + decoding key + issuer thành 1 unit, inject vào AppState B72 (sẽ wire ở B87):
// File: crates/shop-common/src/jwt.rs (continued)
/// Factory + helper encode/decode JWT.
/// Lock B86: factory pattern `new_hs256(secret, issuer)` cho MVP,
/// `new_rs256(private_pem, public_pem, issuer)` G18 nếu cần asymmetric.
pub struct JwtService {
encoding_key: EncodingKey,
decoding_key: DecodingKey,
issuer: String,
}
impl JwtService {
/// Lock B86: HS256 cho MVP monolith Shop API.
/// Secret MUST ≥ 32 bytes random (env var `JWT_SECRET`).
pub fn new_hs256(secret: &str, issuer: String) -> Self {
Self {
encoding_key: EncodingKey::from_secret(secret.as_bytes()),
decoding_key: DecodingKey::from_secret(secret.as_bytes()),
issuer,
}
}
/// Encode claims → JWT string. B87 sẽ wire vào handler login.
pub fn encode(&self, claims: &Claims) -> Result {
let header = Header::new(Algorithm::HS256);
encode(&header, claims, &self.encoding_key)
.map_err(|e| AppError::Internal(format!("jwt encode failed: {}", e)))
}
/// Decode JWT string → claims (verify signature + iss + exp).
/// B88 sẽ dùng trong extractor `CurrentUser`.
pub fn decode(&self, token: &str) -> Result {
let mut validation = Validation::new(Algorithm::HS256);
validation.set_issuer(&[&self.issuer]);
// `exp` validate tự động (default leeway 60s).
decode::(token, &self.decoding_key, &validation)
.map(|data| data.claims)
.map_err(|e| AppError::Unauthenticated(format!("invalid token: {}", e)))
}
}
Re-export top-level cho consumer ngắn gọn:
// File: crates/shop-common/src/lib.rs (extend B10)
pub mod jwt;
pub use jwt::{JwtService, Claims, TokenType};
Note B86: AppError::Unauthenticated(String) variant đã có sẵn từ B10/B16 (lock continued — không thêm variant mới). B87 sẽ wire JwtService vào AppState qua field jwt: Arc<JwtService> và implement endpoint POST /api/v1/auth/login hoàn chỉnh.
Design Shop API Auth Flow
Group 9 sẽ implement đầy đủ 7 endpoint auth + 1 endpoint user (đã có B70):
POST /api/v1/auth/login (B87) — verify password + issue access + refresh
POST /api/v1/auth/refresh (B89) — rotate refresh, issue new access
POST /api/v1/auth/logout (B89) — revoke refresh
POST /api/v1/auth/forgot-password (B91) — send reset token email
POST /api/v1/auth/reset-password (B91) — verify token + update password hash
POST /api/v1/users/verify-email (B70 đã có, B91 audit + gate)
GET /api/v1/users/me (B70 đã có, B88 wire JWT extractor)
POST /api/v1/users/register (B70 đã có)
Token strategy lock vĩnh viễn:
- Access token: JWT HS256, TTL 15 phút, claims Shop API lock B86, dùng cho mọi API request qua
Authorization: Bearer <access>. - Refresh token: opaque random 32-byte hex (KHÔNG JWT), TTL 30 ngày, lưu DB
refresh_tokenstable B90, hash trước khi lưu (cùng pattern password B70). - Rotate refresh on use — mỗi
POST /auth/refreshcấp refresh mới + revoke cũ (markrevoked_at = NOW()). Attacker steal refresh cũ dùng không được lần sau, attacker steal cả 2 (cũ + mới) thì user dùng cũ sẽ bị reject + system detect compromise → invalidate cả family.
Storage strategy lock vĩnh viễn:
- Access token client-side: in-memory (JS variable, React Context, Vuex). KHÔNG dùng
localStorage— XSS attack steal token trivially. KHÔNG dùng cookie — CSRF attack. - Refresh token client-side: HttpOnly Secure SameSite=Strict cookie (web), JS không đọc được chống XSS. Mobile dùng OS keychain (iOS Keychain, Android Keystore, React Native AsyncStorage encrypted).
- Server-side:
refresh_tokenstable (B90) lưu hash + user_id + expires_at + revoked_at + family_id, audit log mọi refresh + revoke event.
Sequence diagram flow chuẩn:
1. Client login → POST /auth/login {email, password}
2. Server verify password Argon2 (B70 lock) → issue access (15m) + refresh (30d)
3. Client store access (memory), refresh (HttpOnly cookie)
4. Client request → Authorization: Bearer <access>
5. Server middleware (B88) verify access → inject Extension<CurrentUser>
6. Access expires (15min) → POST /auth/refresh (cookie tự gửi)
7. Server verify refresh → issue new access + new refresh (rotate)
8. Logout → POST /auth/logout (cookie tự gửi) → revoke refresh
9. Forgot password→ POST /auth/forgot-password {email} → send email reset link
10. Reset password→ POST /auth/reset-password {token, new_password} → invalidate all session
Roadmap Group 9 B86-B95
10 bài Group 9 build incremental, mỗi bài plug vào 11 layer middleware production-grade B85 đã lock:
- B86 (bài này) — JWT overview + structure + algorithm + design auth flow + skeleton module.
- B87 —
POST /auth/loginendpoint +JwtServiceimplement đầy đủ + issue access + refresh + rate limit per-IP (B78 lock) + audit log login action. - B88 — Auth middleware extractor
CurrentUserquaExtension<CurrentUser>(B39 lock continued) + integrate vào router stack INNER hơn rate_limit + wireGET /users/medùngCurrentUserthay placeholder user_id = 1 (B70 TODO). - B89 — Refresh token rotation +
POST /auth/refresh+POST /auth/logout+ revoke pattern + detect compromise (family invalidate). - B90 —
refresh_tokenstable migration + DB layer + hash refresh trước khi lưu + cleanup expired tokens cronjob. - B91 — Password reset flow:
POST /auth/forgot-password+POST /auth/reset-password+ email integration preview G16 + invalidate all session khi reset. - B92 — Email verification gate: middleware require
email_verified = truetrong claims, block endpoint nhạy cảm (checkout, payment) nếu chưa verify; 403 Forbidden. - B93 — Multi-factor authentication (MFA) preview: TOTP RFC 6238 (Time-based One-Time Password) + Google Authenticator compatible + recovery codes.
- B94 — OAuth2/OIDC provider preview: Google Sign-In flow + verify Google ID token + create-or-link Shop user.
- B95 — Auth integration test full flow: login → access protected → expire → refresh → logout → revoke → reset password (test class 7 cho middleware_test.rs B85 extend).
Sau Group 9 đóng (10/10 B95), Shop API có foundation đầy đủ cho Group 10 RBAC (B96-B105): user-role-permission schema, Casbin enforcer, route guard per-permission, attribute-based access control (ABAC) preview.
Tổng Kết
- JWT vs Session: JWT stateless (scale horizontal dễ, microservice friendly), Session stateful (revoke instant, server-side store).
- Shop API chọn JWT (B11 REST stateless lock continued) — mitigate revoke qua refresh rotation + blocklist G15.
- JWT 3-part:
header.payload.signaturebase64url-encode dot-separated. - Payload KHÔNG encrypt — chỉ base64url-encode, ai cũng đọc được qua jwt.io.
- Signature đảm bảo integrity (chống tamper), KHÔNG đảm bảo confidentiality — KHÔNG lưu sensitive PII trong claims.
- HS256 vs RS256: symmetric vs asymmetric — Shop API HS256 monolith MVP, RS256 G18 nếu cần third-party verify hoặc OIDC compliance.
- Claims standard 7 fields RFC 7519 (iss + sub + exp + iat + nbf + jti + aud) + custom (role + email_verified + token_type).
sub+exp+jtiMANDATORY mọi token Shop API;jtibắt buộc cho blocklist revoke instant G15.jsonwebtokencrate v9 workspace dep mới +JwtServicehelper struct skeleton trongshop-common::jwt.- Auth flow lock: access JWT HS256 15 phút + refresh opaque random 30 ngày + rotate on use + detect compromise family invalidate.
- Storage lock: access in-memory (KHÔNG localStorage XSS, KHÔNG cookie CSRF), refresh HttpOnly Secure SameSite=Strict cookie.
- 10 bài Group 9 roadmap (overview + login + middleware + refresh + reset + verify + MFA + OAuth2 + integration test).
- Foundation cho G10 RBAC (B96-B105) sau Group 9 đóng — user-role-permission + Casbin enforcer + route guard middleware.
- File path lock: NEW
crates/shop-common/src/jwt.rs(skeleton B86, implement đầy đủ B87); future NEWcrates/shop-api/src/middleware/auth.rs(B88 wireExtension<CurrentUser>).
Bài Tập Củng Cố
Tự trả lời, đáp án ở cuối:
- JWT vs Session cookie — trade-off chính là gì? Cho ví dụ scenario cụ thể mà revoke instant cần thiết (Session thắng JWT).
- JWT payload KHÔNG encrypt — pitfall lưu sensitive field. Cho ví dụ scenario dev nhúng
password_hashhoặccredit_cardvào claims gây leak gì cụ thể. - HS256 vs RS256 — khi nào dùng asymmetric? Cho ví dụ scenario third-party verify token mà HS256 KHÔNG giải được.
- Claims standard 7 fields RFC 7519 — phân biệt
iss/sub/exp/iat/jti. Tại saojtiMANDATORY cho blocklist? Không cójtithì revoke thế nào? - Refresh token rotation — pattern "rotate on use". Cho ví dụ scenario attacker steal refresh token: rotate giải được như thế nào? Family invalidate là gì?
Đáp án
- JWT vs Session trade-off + scenario revoke instant cần thiết: trade-off chính là stateless vs stateful. JWT stateless = server không cần store, scale horizontal dễ (chỉ share secret), microservice friendly (service A issue, service B verify); cons: revoke KHÔNG instant (token valid tới expiry kể cả đã đổi password). Session stateful = server có store (Redis/DB) lookup mỗi request; cons: cần shared store, latency 1 round-trip Redis, scale khó. Scenario revoke instant cần thiết: (a) banking app — user báo mất điện thoại, admin click "logout all devices" → cần revoke instant mọi session để chống unauthorized access tiếp; JWT phải đợi 15 phút access expire + revoke refresh, attacker vẫn dùng access token cũ tới 15 phút sau (Shop API mitigate qua blocklist G15 + short TTL 15 phút balance trade-off). (b) admin dashboard fire employee — IT cần revoke quyền truy cập ngay lập tức cho compliance audit (SOC 2 require < 1 phút); Session delete instant, JWT cần blocklist. (c) compromised account scenario — bot detect login lạ → instant disable; Session delete = chặn 100%, JWT vẫn rủi ro 15 phút. Pattern lock Shop API: chấp nhận trade-off 15 phút stale qua short-lived access + bù qua refresh rotation + blocklist Redis G15 cho critical revoke event (force_logout endpoint).
- JWT payload KHÔNG encrypt pitfall: payload chỉ base64url-encode (KHÔNG encrypt) → mọi người có token đều decode được bằng
base64 -dhoặc jwt.io trong 5 giây. Scenario nhúngpassword_hash: dev thêm"password_hash": "$argon2id$v=19$m=65536,t=3,p=4$salt$hash"vào claims để "tiện check" sau này → token bị leak (log server, browser history, CDN cache, sentry breadcrumb) → attacker decode payload → có Argon2 hash → offline brute-force với GPU rig (Argon2 chậm nhưng vẫn tốn 1 lần hash/giây thay vì 1 tỷ lần/giây như MD5) → nếu password yếu (< 10 char common word) → crack được trong 24h → login real user. Scenario nhúngcredit_card: dev nhúng"card_last4": "4242"+"card_token": "tok_xxx"để frontend hiển thị "Thanh toán với thẻ ****4242" → token bị log accidentally vào CloudWatch/Datadog (engineer xem log không cần auth) → leak PCI DSS data → vi phạm regulation phạt $50K-500K per incident + audit fail. Pattern lock vĩnh viễn: claims chỉ chứa identifier (user_id) + capability (role) + state flag (email_verified) + metadata (exp/jti); mọi sensitive data lưu DB query khi cần — pattern "claims minimal + DB lookup khi cần data nhạy cảm" trade-off latency 1 query đổi security. - HS256 vs RS256 — scenario asymmetric cần thiết: HS256 dùng 1 secret cho cả sign + verify → mọi service verify token phải có secret → service đó cũng sign được token giả → single trust boundary. Scenario third-party verify HS256 KHÔNG giải được: (a) Shop API muốn cho phép mobile SDK third-party (vd merchant partner build app vendor riêng) verify token user Shop → KHÔNG thể share HS256 secret với partner (partner sign token giả mạo user khác = bypass auth); RS256 cho partner public key — partner verify được token Shop đã sign nhưng không sign được (private key giữ ở Shop API server). (b) Microservice architecture cùng team nhưng different security tier: shop-api production cluster sign token với private key + shop-analytics worker (less secure tier, internal network) verify với public key — nếu shop-analytics bị compromise, attacker KHÔNG sign được token giả vì không có private key. (c) OIDC compliance ecosystem — Google/Auth0/Keycloak/Okta đều dùng RS256 + JWKS endpoint
/.well-known/jwks.jsonserve public key, integration phải dùng RS256 để verify Google ID token; HS256 không có cách share secret với Google. Pattern lock Shop API: HS256 monolith MVP đủ (1 trust boundary), RS256 G18 deploy khi cần partner verify hoặc multi-cluster cross-region trust boundary. - Claims standard phân biệt:
iss(issuer) = ai phát hành token (Shop API"shop.blogcode.vn") — verify chống token từ issuer khác trộn vào (vd token từ"auth.partner.com"mà service Shop nhận);sub(subject) = user_id token thuộc về (string "1") — primary key để map sang user table;exp(expiration) = Unix timestamp seconds token hết hạn — MANDATORY chống token vĩnh viễn (attacker steal token 1 lần dùng đời đời);iat(issued at) = Unix timestamp lúc phát hành — debug ("token này issue khi nào?") + tính age token cho monitoring;jti(JWT ID) = UUID v4 unique mỗi token — MANDATORY cho blocklist revoke. Tại saojtiMANDATORY blocklist: blocklist Redis G15 lưu keyblocklist:<jti>với TTL = remaining lifetime của token; khi user logout/admin revoke, INSERT key vào Redis; middleware verify sau khi check signature + exp thì query RedisEXISTS blocklist:<jti>→ có thì reject 401. Không cójti= không có ID unique để put vào blocklist, phải lưu cả token string (lãng phí 500 bytes/token vs 16 bytes UUID) hoặc hash của token (vẫn lãng phí + thêm overhead hash mỗi request). Alternative không có blocklist: dùngiat+ bảngusers.password_changed_at— token cóiat < user.password_changed_atthì invalid → revoke all sessions khi đổi password; nhưng pattern này chỉ revoke được bulk all sessions, không revoke 1 token cụ thể như blocklistjti. Lock pattern Shop API:jtiUUID v4 MANDATORY + blocklist Redis G15 +iat+password_changed_atbulk revoke combo cover mọi case. - Refresh rotation + scenario attacker steal + family invalidate: rotate on use = mỗi
POST /auth/refreshserver cấp refresh token mới + mark refresh cũrevoked_at = NOW(); client lưu refresh mới, refresh cũ dùng lại bị reject. Scenario attacker steal refresh: (a) attacker steal refresh từ XSS browser victim (cookie HttpOnly chống được, nhưng phishing/keylogger vẫn steal được nếu user nhập sai field); (b) attacker dùng refresh cũ → server cấp access + refresh mới cho attacker → attacker login thành công; (c) victim user dùng lại Shop app → refresh cũ đã rotate ở step (b) → server reject 401 → user thấy bị logout đột ngột → user nghi ngờ + đổi password. Nếu không có rotate: attacker dùng refresh cũ 30 ngày không bị detect → silent compromise. Rotate giúp detect compromise qua sự kiện "refresh cũ bị dùng lại sau khi đã rotate". Family invalidate = mỗi refresh chain (login → refresh → refresh → ...) gánfamily_idUUID chung (B90 column DB); khi server detect refresh cũ đã revoked được dùng lại (signal compromise) → invalidate toàn bộ family (UPDATE refresh_tokens SET revoked_at = NOW() WHERE family_id = $1) → cả attacker và victim đều bị logout → user buộc login lại + đổi password → cắt session attacker triệt để. Pattern lock Shop API: family_id UUID per login session + rotate on use + detect compromise → family invalidate + audit log compromise event → notify user email (preview G16). Industry adoption: Auth0 + Firebase + AWS Cognito đều dùng pattern rotation + family invalidate (RFC 6819 OAuth2 Threat Model §5.2.2.3).
Bài Tiếp Theo
Bài 87: POST /auth/login Endpoint — Implementation — implement POST /api/v1/auth/login: verify password Argon2 (B70 lock), issue access + refresh token qua JwtService B86 skeleton wire đầy đủ, return token pair + user info, rate limit per-IP (B78 lock continued chống brute-force), audit log login action (B62 lock).
