Mở đầu — vì sao bài này quan trọng
Hãy hình dung bạn là PM của một app fintech tại Việt Nam. Một sáng thứ Hai, bộ phận chăm sóc khách hàng báo cáo: hàng loạt người dùng phàn nàn họ "bị đăng xuất liên tục", phải đăng nhập lại mỗi 5 phút. Tệ hơn, một vài tài khoản nghi ngờ bị truy cập trái phép. Engineer nói với bạn: "Vấn đề ở cơ chế authentication, mình đang dùng JWT nhưng không có refresh token đúng cách, mà revoke thì không revoke được." Nếu bạn không hiểu Session, JWT và OAuth2 thực sự khác nhau ở đâu, bạn sẽ ngồi nghe như nghe ngoại ngữ, không thể đánh giá phương án, không ước lượng được rủi ro, và không thể quyết định.
Authentication (xác thực — "bạn là ai") là một trong những phần technical mà PM bắt buộc phải nắm, vì nó nằm ngay giao điểm của ba thứ bạn quan tâm hàng ngày: trải nghiệm người dùng (đăng nhập có mượt không, có bị logout oan không), bảo mật (tài khoản có bị chiếm không), và chi phí kỹ thuật (lưu state ở đâu, scale ra sao). Lựa chọn Session hay JWT hay OAuth2 không phải là quyết định "kỹ thuật thuần túy" — nó ảnh hưởng trực tiếp đến roadmap, đến khả năng tích hợp đối tác, và đến rủi ro pháp lý.
Trong bài này, tôi sẽ giúp bạn hiểu rõ ba cơ chế authentication phổ biến nhất, khi nào dùng cái nào, và quan trọng nhất — biết đặt câu hỏi đúng khi ngồi cùng đội engineering. Đây thuần về authentication mechanics; phần phân biệt sâu giữa Authentication và Authorization sẽ có ở Bài 41, còn ở đây ta tập trung vào "làm sao chứng minh bạn là bạn".
Khái niệm cốt lõi
Mọi cơ chế authentication đều giải quyết cùng một bài toán: sau khi người dùng đăng nhập thành công lần đầu (nhập email + mật khẩu), làm sao server nhớ rằng những request tiếp theo là của chính người đó, mà không bắt họ nhập lại mật khẩu mỗi lần? HTTP vốn là stateless (không nhớ gì giữa các request — bạn đã học ở Bài 6), nên ta cần một "tấm vé" để client mang theo. Ba cách tạo và quản lý tấm vé đó chính là Session, JWT, và OAuth2.
Session-based Authentication
Đây là cách kinh điển, ra đời cùng web. Luồng như sau:
- Người dùng đăng nhập. Server kiểm tra mật khẩu đúng, rồi tạo một session ID ngẫu nhiên (ví dụ một chuỗi 128-bit như
s_a8f3...). - Server lưu state gắn với session ID này ở phía server — thường trong một bộ nhớ nhanh như Redis hoặc trong database (ví dụ:
s_a8f3 → user_id 1024, role: customer, login_at: ...). - Server trả session ID về cho client, lưu trong một cookie.
- Mỗi request sau đó, trình duyệt tự động gửi cookie chứa session ID. Server tra Redis, thấy session hợp lệ, biết ngay đây là user 1024.
- Logout = xóa session khỏi Redis. Lập tức tấm vé vô hiệu, dù cookie vẫn còn trên máy client.
Token-based Authentication với JWT
JWT (JSON Web Token) lật ngược triết lý: thay vì lưu state ở server, ta đóng gói thông tin vào chính tấm vé và để client giữ. Một JWT gồm 3 phần ngăn bởi dấu chấm: header.payload.signature.
- Payload chứa các "claim" như
user_id: 1024,role: customer,exp: <thời điểm hết hạn>. Phần này chỉ được mã hóa Base64, KHÔNG mã hóa bí mật — ai cũng đọc được, nên tuyệt đối không nhét mật khẩu hay dữ liệu nhạy cảm vào đây. - Signature là chữ ký số, server ký bằng một khóa bí mật. Nó đảm bảo token không bị giả mạo: nếu kẻ tấn công sửa
role: customerthànhrole: admin, chữ ký sẽ không khớp và server từ chối ngay.
Nhưng đây cũng là gót chân Achilles: JWT khó thu hồi (revoke). Vì server không lưu state, một khi đã phát token có hạn 1 giờ, bạn không có cách dễ dàng để vô hiệu nó giữa chừng. Người dùng đổi mật khẩu vì nghi bị hack? Token cũ vẫn sống đến khi hết hạn. Giải pháp thực tế là dùng cặp access token (ngắn hạn, 15 phút) + refresh token (dài hạn, lưu ở server và CÓ THỂ revoke) — và lúc này, mỉa mai thay, bạn đã quay lại lưu state một phần ở server rồi.
OAuth2 — ủy quyền truy cập
OAuth2 giải quyết bài toán khác hẳn: làm sao để app của bạn thay mặt người dùng truy cập tài nguyên ở một dịch vụ khác mà không cần người dùng đưa mật khẩu? Đây chính là cơ chế đằng sau nút "Đăng nhập bằng Google" hay "Liên kết tài khoản Momo".
Luồng phổ biến nhất (Authorization Code Flow):
- App của bạn chuyển hướng người dùng sang trang đăng nhập của nhà cung cấp (Google/Facebook/Zalo).
- Người dùng đăng nhập trực tiếp tại Google và đồng ý cấp quyền ("App này muốn đọc email của bạn — Đồng ý?").
- Google trả về một authorization code ngắn hạn cho app bạn.
- App bạn đổi code này lấy access token (thường là JWT) ở backend.
- Dùng access token đó gọi API của Google thay mặt người dùng.
Tình huống thực tế
Ví dụ 1: Sàn TMĐT chuyển từ Session sang JWT khi scale — và trả giá
Một sàn thương mại điện tử giả định tên ChợViệt ban đầu dùng session-based với Redis, chạy tốt với 50.000 user. Khi lên 2 triệu user dịp sale 12/12, mỗi request đều phải lookup Redis, và cụm Redis trở thành nút thắt cổ chai — latency tăng vọt giờ cao điểm. Đội engineering đề xuất chuyển sang JWT để bỏ lookup, scale stateless.
Họ chuyển sang JWT với access token hạn 24 giờ, không có refresh token đàng hoàng. Hai tháng sau, sự cố: một nhân viên bị lộ tài khoản admin. Họ đổi mật khẩu ngay, nhưng kẻ tấn công vẫn dùng JWT cũ thao túng đơn hàng suốt gần một ngày — vì không revoke được token. Thiệt hại vài trăm triệu đồng.
Bài học: JWT giải quyết bài toán scale nhưng tạo ra bài toán revoke. Lẽ ra PM phải hỏi "nếu cần đăng xuất khẩn cấp một tài khoản thì làm thế nào?" trước khi duyệt. Giải pháp đúng: access token 15 phút + refresh token lưu Redis (revoke được). Như vậy lookup chỉ xảy ra mỗi 15 phút thay vì mỗi request — vừa scale, vừa kiểm soát.
Ví dụ 2: App gọi xe tích hợp "Đăng nhập bằng Google" để giảm ma sát
Một startup gọi xe tại TP.HCM nhận thấy 38% người dùng bỏ cuộc ngay ở màn hình đăng ký vì ngại nhập email + tạo mật khẩu + xác minh OTP. PM đề xuất thêm "Đăng nhập bằng Google" qua OAuth2.
Sau khi triển khai, tỷ lệ hoàn tất đăng ký tăng từ 62% lên 84%, thời gian onboarding trung bình giảm từ 95 giây xuống 22 giây. Người dùng không phải nghĩ ra mật khẩu mới, và đội engineering không phải lưu trữ — không phải chịu rủi ro lộ — mật khẩu của họ.
Bài học: OAuth2 không chỉ là chuyện kỹ thuật, nó là đòn bẩy cho metric conversion. Nhưng PM cũng cần lường trước: phải xử lý trường hợp một người dùng có cả tài khoản email cũ lẫn tài khoản Google cùng một địa chỉ — gọi là account linking. Bỏ qua chi tiết này sẽ tạo ra tài khoản trùng và dữ liệu phân mảnh.
Ví dụ 3: Ngân hàng số yêu cầu Session vì lý do tuân thủ
Một ngân hàng số tại Việt Nam phải tuân thủ quy định của Ngân hàng Nhà nước về khả năng chấm dứt phiên tức thì: nếu phát hiện giao dịch bất thường, hệ thống phải đăng xuất người dùng trong dưới 1 giây. Đội kiến trúc đề xuất JWT vì "hiện đại hơn", nhưng PM — sau khi hiểu bản chất — đã phản biện: JWT stateless không revoke tức thì được; chỉ session-based mới đảm bảo xóa-là-mất-quyền ngay lập tức.
Cuối cùng họ giữ session-based cho luồng giao dịch nhạy cảm, chấp nhận chi phí Redis lookup để đổi lấy khả năng kiểm soát tuyệt đối.
Bài học: "Công nghệ mới hơn" không đồng nghĩa "phù hợp hơn". Yêu cầu tuân thủ (compliance) đôi khi quyết định lựa chọn kỹ thuật, và PM là người nối ranh giới giữa pháp lý và engineering.
Hướng dẫn từng bước
Khi đối diện một quyết định về authentication, bạn có thể đi theo quy trình sau:
- Xác định bài toán thật sự. Bạn cần "đăng nhập nội bộ" (Session/JWT) hay "cho phép app khác/đối tác truy cập thay mặt user" (OAuth2)? Đừng dùng OAuth2 cho mọi thứ chỉ vì nó "xịn".
- Hỏi về yêu cầu revoke. Đặt câu hỏi: "Nếu cần đăng xuất khẩn cấp một tài khoản trong vòng vài giây thì hệ thống làm được không?" Nếu câu trả lời là "không, phải đợi token hết hạn", và sản phẩm của bạn là fintech/banking/y tế — đó là cờ đỏ.
- Cân nhắc quy mô và kiến trúc. Một monolith vừa phải với traffic ổn định → session-based đơn giản, dễ kiểm soát. Hệ thống microservices nhiều service, traffic lớn → JWT giúp tránh lookup tập trung.
- Thiết kế vòng đời token. Nếu dùng JWT, luôn yêu cầu cặp access token ngắn hạn + refresh token. Hỏi rõ: access token hạn bao lâu? Refresh token lưu ở đâu, revoke thế nào?
- Xử lý lưu trữ phía client an toàn. Token nên nằm trong cookie
HttpOnly(JavaScript không đọc được, chống XSS) thay vìlocalStorage. Đây là chi tiết bạn nên xác nhận với engineer trong technical spec (Bài 12).
- Lập kế hoạch cho các edge case. Đăng nhập đa thiết bị, account linking khi dùng OAuth2, hành vi khi token hết hạn giữa thao tác. Những thứ này phải nằm trong Acceptance Criteria (Bài 18), không để engineer tự đoán.
Lỗi thường gặp & mẹo
Lỗi: Nhét dữ liệu nhạy cảm vào JWT payload. Vì payload chỉ Base64, không mã hóa, mọi người đều đọc được. Đừng bao giờ để mật khẩu, số CCCD, hay thông tin thẻ trong đó. Mẹo: vào trang jwt.io, dán một token vào và tự mình đọc payload — bạn sẽ "ngộ" ra ngay vì sao không được tin payload là bí mật.
Lỗi: Coi JWT là "đăng xuất được như session". Đây là hiểu lầm phổ biến nhất. JWT thuần stateless KHÔNG revoke được. Mẹo: luôn ghép với refresh token có lưu state nếu cần khả năng đăng xuất chủ động.
Lỗi: Lưu token trong localStorage. Dễ bị đánh cắp qua tấn công XSS (sẽ học ở Bài 40 — OWASP Top 10). Mẹo: ưu tiên cookie HttpOnly + Secure + SameSite.
Lỗi: Đặt thời hạn token quá dài "cho tiện". Token 30 ngày nghĩa là kẻ trộm được token có 30 ngày tự do. Mẹo: access token nên ngắn (15 phút), bù lại bằng refresh token để trải nghiệm vẫn mượt.
Lỗi: PM gật đầu mọi đề xuất authentication vì "không hiểu nên tin tưởng". Mẹo: bạn không cần code được, nhưng phải hỏi được ba câu — revoke thế nào, token lưu ở đâu, hạn bao lâu. Ba câu này lọc ra 80% rủi ro.
Bài tập thực hành
- Đọc một JWT thật. Vào jwt.io, tạo hoặc dán một token mẫu, đọc kỹ ba phần header/payload/signature. Viết ra: phần nào ai cũng đọc được, phần nào chống giả mạo, và vì sao.
- Bài tập quyết định. Cho ba sản phẩm sau, chọn cơ chế phù hợp và giải thích trong 3–4 câu mỗi cái: (a) một blog cá nhân đơn giản; (b) một super-app với 20 microservices và 5 triệu user; (c) một ví điện tử cần đăng xuất tài khoản nghi gian lận trong dưới 1 giây.
- Viết câu hỏi cho engineer. Giả sử đội đề xuất "dùng JWT cho hệ thống đặt lịch khám bệnh". Viết ra 5 câu hỏi bạn sẽ đặt để đánh giá rủi ro trước khi duyệt vào technical spec.
- Vẽ luồng OAuth2. Vẽ tay sơ đồ "Đăng nhập bằng Google" với đủ các bước chuyển hướng. Chỉ rõ điểm mà mật khẩu Google KHÔNG bao giờ đi qua hệ thống của bạn.
Tóm tắt
Ba cơ chế, ba triết lý. Session-based lưu state ở server (thường Redis), cho bạn kiểm soát tuyệt đối và khả năng revoke tức thì, đổi lại phải lookup mỗi request — lý tưởng cho fintech, banking, và những nơi cần đăng xuất khẩn cấp. JWT đóng gói thông tin vào tấm vé, stateless, scale ngang dễ dàng cho microservices — nhưng khó revoke, nên gần như luôn cần ghép access token ngắn hạn với refresh token. OAuth2 giải bài toán ủy quyền — cho phép app truy cập thay mặt người dùng mà không cần mật khẩu, là nền tảng của "Đăng nhập bằng Google/Zalo" và là đòn bẩy mạnh cho conversion.
Là Technical PM, bạn không cần tự tay triển khai, nhưng bạn phải biết đặt đúng câu hỏi: cần revoke không, lưu token ở đâu, hạn bao lâu, và bài toán thật sự là đăng nhập hay ủy quyền. Trả lời được bốn câu đó, bạn đã đủ tầm để dẫn dắt một quyết định authentication đúng đắn — vừa an toàn, vừa mượt, vừa scale được.