Khi thiết kế luồng đăng nhập và kiểm soát truy cập API cho hệ thống backend, câu hỏi kiến trúc cốt lõi thường là: khi nào nên duy trì Session, khi nào dùng JWT, và OAuth2 hay OpenID Connect (OIDC) đứng ở vị trí nào? Trong video "7 Authentication Concepts Every Developer Should Know", tác giả Hayk Simonyan đã hệ thống lại các khái niệm này nhằm giải quyết những nhầm lẫn phổ biến giữa các cơ chế bảo mật.

Phân định giữa Authentication và Authorization

Điểm mấu chốt đầu tiên là tách bạch hai khái niệm thường bị đánh đồng:

  • Authentication (AuthN): Xác thực danh tính – trả lời câu hỏi "Chủ thể đang gửi request là ai?".

  • Authorization (AuthZ): Phân quyền – trả lời câu hỏi "Chủ thể này có quyền thực hiện hành động gì trên tài nguyên?".

OAuth2 là một authorization framework, không phải cơ chế xác thực danh tính người dùng. Ngược lại, Session là cơ chế duy trì danh tính đã xác thực, còn OIDC giải quyết bài toán AuthN. JWT thực chất là một định dạng dữ liệu (token format), còn Bearer token là một phương thức sử dụng (ai nắm giữ token thì có thể trình token đó để yêu cầu quyền truy cập).

Chúng phối hợp với nhau như thế nào? OIDC và Session không hề loại trừ lẫn nhau mà thường bổ trợ trực tiếp: ứng dụng sử dụng OIDC để người dùng đăng nhập qua một Identity Provider (IdP) tập trung; sau khi client nhận và xác thực id_token thành công, backend của ứng dụng sẽ tự khởi tạo một Session cục bộ (kèm cookie) để duy trì danh tính đã xác thực cho người dùng trên hệ thống của mình.

Luồng Session-based truyền thống

Trong mô hình xác thực dựa trên phiên (stateful), người dùng gửi thông tin đăng nhập lên server. Khi xác thực thành công, server tạo một session ID và lưu trạng thái phiên vào bộ lưu trữ (có thể là bộ nhớ cục bộ trên một máy chủ đơn lẻ, hoặc một shared store như database hoặc bộ nhớ đệm phân tán khi cần mở rộng quy mô nhiều node).

Session ID sau đó được gửi về trình duyệt qua cookie. Ở các request tiếp theo, trình duyệt tự động đính kèm cookie này; server tra cứu session store để khôi phục danh tính người dùng. Ưu điểm của mô hình này là khả năng kiểm soát vòng đời phiên rất chặt chẽ: server có thể chủ động hủy phiên làm việc ngay lập tức. Đổi lại, server bắt buộc phải tra cứu trạng thái phiên ở mỗi lượt truy cập.

Luồng Token-based và cơ chế xác minh JWT

Khác với Session ID là chuỗi ngẫu nhiên không tự chứa thông tin (opaque string), JSON Web Token (JWT) là định dạng token tự chứa dữ liệu (self-contained). Server ký số (JWS) lên một payload chứa các claims như mã định danh chủ thể (sub), vai trò và thời gian hết hạn (exp). Cần lưu ý rằng chữ ký số không đồng nghĩa với mã hóa: phần payload của signed JWT chỉ được encode Base64URL và có thể đọc được công khai nếu không áp dụng thêm chuẩn mã hóa (JWE).

Khi client gửi token qua header (thường dưới dạng Bearer token), API server có thể tự kiểm tra tính hợp lệ của chữ ký và các claims cần thiết (như issuer, audience, expiry) ngay tại chỗ. Cơ chế này giúp giảm bớt các lượt truy vấn database chỉ để phục vụ mục đích xác thực danh tính. Tuy nhiên, việc thu hồi token trước hạn sẽ cần thiết kế thêm giải pháp bổ trợ (chẳng hạn như duy trì danh sách token bị thu hồi).

Trong kiến trúc token phổ biến, hệ thống thường kết hợp hai loại token:

  • Access Token: Có thời gian sống ngắn (ví dụ 15–60 phút), dùng để gửi kèm các request gọi resource API.

  • Refresh Token: Có thời gian sống dài hơn (vài ngày hoặc vài tuần), chỉ dùng để gửi về Auth Server xin cấp Access Token mới khi token cũ hết hạn.

Để hạn chế việc mã độc JavaScript đọc được token trong trường hợp ứng dụng gặp lỗi XSS, Refresh Token thường được lưu trong cookie có gắn cờ HttpOnly (dù vậy, cơ chế này chỉ giảm thiểu khả năng đọc trộm cookie từ script chứ không ngăn chặn được toàn bộ các hệ quả khác của XSS).

OAuth2: Ủy quyền truy cập tài nguyên

OAuth2 giải quyết bài toán ủy quyền truy cập cho ứng dụng mà không yêu cầu người dùng cung cấp mật khẩu trực tiếp.

Trong ví dụ của video, một ứng dụng muốn truy cập tệp tin trên Google Drive của người dùng. Ứng dụng sẽ chuyển hướng người dùng sang Google để hiển thị màn hình xin cấp quyền (consent screen). Sau khi người dùng đồng ý, Google trả về authorization code. Ứng dụng gửi code này về IdP để đổi lấy access_token. Khi ứng dụng đính kèm access_token để gọi Google Drive API, token này chỉ đóng vai trò là bằng chứng chứng minh ứng dụng được phép truy cập tài nguyên trong phạm vi (scopes) đã duyệt. Bản thân nó không được thiết kế để xác minh danh tính người dùng cho ứng dụng.

OpenID Connect (OIDC): Lớp định danh trên nền OAuth2

Để chuẩn hóa việc xác thực người dùng, OIDC bổ sung lớp định danh ngay trên nền tảng của OAuth2. Khi ứng dụng yêu cầu scope openid, quá trình đổi authorization code sẽ trả về cả access_token lẫn id_token.

  • ID Token: Là một JWT chứa thông tin định danh của người dùng (tối thiểu là subject identifier sub, cùng các thông tin khác như email nếu được cấu hình và người dùng đồng ý chia sẻ). Ứng dụng client tiến hành xác minh chữ ký số, nhà phát hành (iss), đối tượng nhận (aud) và thời hạn (exp) của ID Token để xác nhận danh tính người dùng.

  • Access Token: Được dùng riêng cho việc gọi sang các resource API cần kiểm tra quyền hạn.

Các mảnh ghép còn lại

Bức tranh xác thực và truy cập còn bao gồm các cơ chế phụ trợ:

  • API Key: Thường dùng cho giao tiếp giữa các dịch vụ hoặc client bên ngoài. Người giữ key chỉ có quyền thực hiện thao tác trong phạm vi (scopes) được gán sẵn; thời hạn hiệu lực và cơ chế xoay vòng key phụ thuộc vào chính sách của từng hệ thống.

  • Basic & Digest Auth: Basic Auth gửi thông tin tài khoản được encode Base64 (không phải mã hóa) nên bắt buộc phải chạy qua HTTPS. Digest Auth cải tiến hơn bằng cơ chế challenge-response (nguồn minh họa thuật toán băm MD5, dù các tiêu chuẩn cập nhật hỗ trợ các hàm băm mạnh hơn như SHA-256).

  • Single Sign-On (SSO): Là mô hình kiến trúc cho phép người dùng đăng nhập một lần tại Identity Provider (IdP) để truy cập nhiều ứng dụng độc lập dựa trên mối quan hệ tin cậy với IdP đó. Về mặt giao thức, SAML trao đổi assertion dạng XML; OIDC dùng ID token dạng JWT.

Khi triển khai thực tế trên hệ thống backend của mình, anh em đang ưu tiên duy trì phiên đăng nhập bằng Session tập trung hay dùng cặp Access/Refresh Token với giải pháp thu hồi riêng?