Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

JWT (JSON Web Token): cấu trúc và use case

API and Technical Fundamentals cho BA Bài 18/60

Mở đầu — vì sao bài này quan trọng

Nếu bạn đã đọc qua Bài 16 về Authentication overview, bạn sẽ nhớ rằng có ba kiểu xác thực phổ biến: Basic, Bearer và API Key. JWT chính là "ngôi sao" đứng sau phần lớn các Bearer token mà bạn gặp ngày nay. Khi một backend developer nói với bạn "API này dùng JWT", hay khi bạn đọc một tài liệu kỹ thuật thấy dòng Authorization: Bearer eyJhbGci..., thứ đang nằm trong đầu họ chính là JSON Web Token.

Là một BA, bạn không cần tự tay ký (sign) hay verify token. Nhưng bạn cần hiểu JWT đủ sâu để làm ba việc cực kỳ thực tế: (1) viết được user story và acceptance criteria liên quan đến phiên đăng nhập, hết hạn token, refresh token; (2) ngồi họp với team kỹ thuật mà không bị "lạc trôi" khi họ tranh luận về stateless và session; và (3) lường trước được các rủi ro bảo mật và trải nghiệm khi thiết kế luồng đăng nhập cho sản phẩm.

Lý do JWT quan trọng đến vậy nằm ở một ý tưởng đơn giản nhưng thay đổi cách hệ thống vận hành: token tự đóng gói thông tin. Server không cần tra cứu database mỗi lần kiểm tra "anh là ai" — chỉ cần giải mã (decode) token là biết. Điều này nghe có vẻ thuần kỹ thuật, nhưng nó ảnh hưởng trực tiếp đến chi phí hạ tầng, tốc độ phản hồi, và cả những quyết định nghiệp vụ như "khi nào người dùng bị bắt đăng nhập lại". Hiểu được cơ chế này, bạn sẽ là BA mà developer thực sự muốn ngồi họp cùng.

Khái niệm cốt lõi

JWT là gì — token tự đóng gói

Hãy tưởng tượng JWT như một tấm vé xem phim đã được đóng dấu mộc nổi của rạp. Trên tấm vé in sẵn: tên phim, suất chiếu, số ghế, hạn sử dụng. Nhân viên soát vé chỉ cần nhìn tấm vé và kiểm tra con dấu là cho bạn vào — họ không cần gọi điện về văn phòng để hỏi "vé này có thật không". Con dấu mộc nổi rất khó làm giả, nên nó đủ để chứng minh tính hợp lệ.

JWT hoạt động đúng như vậy. Bên trong token đã chứa sẵn thông tin về người dùng (ai, quyền gì, hết hạn lúc nào), và một "con dấu số" gọi là chữ ký (signature). Server nhận token, kiểm tra chữ ký, nếu hợp lệ thì tin toàn bộ thông tin bên trong mà không cần truy vấn database. Đây gọi là stateless — server không phải lưu trạng thái phiên đăng nhập ở đâu cả.

Cấu trúc: 3 phần phân tách bằng dấu chấm

Một JWT là một chuỗi dài trông như vô nghĩa, nhưng thực ra gồm ba phần ngăn cách bởi dấu chấm (.):

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.
eyJzdWIiOiIxMjM0NSIsIm5hbWUiOiJOZ3V5ZW4gVmFuIEEiLCJyb2xlIjoiYWRtaW4iLCJleHAiOjE3MTk0ODg4MDB9
.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Ba phần đó là: Header . Payload . Signature.

Phần 1 — Header (phần đầu): Cho biết loại token và thuật toán dùng để ký. Khi giải mã ra, nó trông như thế này:

{
  "alg": "HS256",
  "typ": "JWT"
}

alg là thuật toán ký (ví dụ HS256, RS256), typ xác nhận đây là JWT.

Phần 2 — Payload (phần thân, chứa dữ liệu): Đây là phần thú vị nhất với BA, vì nó chứa các claim — những "tuyên bố" về người dùng. Giải mã ra:

{
  "sub": "12345",
  "name": "Nguyen Van A",
  "role": "admin",
  "exp": 1719488800
}

Có ba nhóm claim: claim tiêu chuẩn (sub = subject/ID người dùng, exp = thời điểm hết hạn, iat = thời điểm phát hành, iss = bên phát hành), claim công khai, và claim tùy chỉnh do hệ thống tự định nghĩa (như role, tenant_id, email).

Cảnh báo cực kỳ quan trọng: Header và Payload chỉ được mã hóa Base64URL, KHÔNG phải mã hóa bảo mật. Bất kỳ ai cầm được token đều có thể giải mã và đọc toàn bộ Payload — bạn có thể tự kiểm chứng bằng cách dán token vào trang jwt.io. Vì vậy tuyệt đối không bao giờ để thông tin nhạy cảm (mật khẩu, số CMND/CCCD, số thẻ tín dụng) vào Payload.

Phần 3 — Signature (chữ ký): Đây là "con dấu mộc nổi". Server tạo ra nó bằng cách lấy Header + Payload, kết hợp với một khóa bí mật (secret key) mà chỉ server biết, rồi chạy qua thuật toán ký. Nếu ai đó sửa một ký tự trong Payload (ví dụ đổi "role": "user" thành "role": "admin"), chữ ký sẽ không khớp nữa, và server lập tức từ chối token. Đây chính là cơ chế chống giả mạo.

Vòng đời của một JWT

Luồng điển hình diễn ra như sau: Người dùng đăng nhập bằng email + mật khẩu → Server xác thực đúng → Server tạo JWT, ký bằng secret key, trả về cho client → Client lưu token (thường ở localStorage hoặc cookie) → Mỗi request sau đó, client đính token vào header Authorization: Bearer <token> → Server verify chữ ký và đọc thông tin → cho phép truy cập.

JWT vs Session truyền thống

Trước JWT, cách phổ biến là dùng session: khi đăng nhập, server tạo một session ID, lưu thông tin phiên vào database/Redis, rồi gửi session ID về cho client. Mỗi request, server phải tra cứu database để biết session ID đó tương ứng với ai. Cách này gọi là stateful.

JWT lật ngược điều này: thông tin nằm trong chính token (stateless), nên server không cần tra cứu. Ưu điểm là dễ mở rộng (scale) ra nhiều server, nhanh hơn vì bỏ được bước query. Nhược điểm lớn nhất: vì server không lưu trạng thái, nó rất khó thu hồi (revoke) một token trước khi nó hết hạn. Đây là điểm đánh đổi mà BA phải nắm chắc, ta sẽ thấy hậu quả thực tế ngay phần dưới.

Tình huống thực tế

Tình huống 1 — Sàn TMĐT và bài toán "đăng xuất tức thì"

Một startup thương mại điện tử ở TP.HCM, tạm gọi là ShopViet, xây dựng app mobile dùng JWT với thời hạn exp là 24 giờ. Mọi thứ chạy mượt cho đến khi đội Chăm sóc khách hàng báo lên một sự cố: một nhân viên kho bị sa thải vì gian lận, tài khoản đã bị "vô hiệu hóa" trong hệ thống admin, nhưng người này vẫn truy cập được vào hệ thống nội bộ suốt gần một ngày sau đó.

Nguyên nhân: token của người này đã được phát hành với exp 24 giờ. Vì JWT là stateless, server vẫn chấp nhận token đó cho đến khi nó tự hết hạn — việc "vô hiệu hóa tài khoản" trong database không hề ảnh hưởng đến token đang lưu trên máy nhân viên.

Diễn giải: Team kỹ thuật giải quyết bằng cách rút ngắn exp của access token xuống 15 phút và bổ sung refresh token (token dài hạn dùng để xin access token mới). Khi tài khoản bị khóa, refresh token bị thu hồi trong database, nên sau tối đa 15 phút người dùng sẽ bị "đá" ra.

Bài học cho BA: Khi viết acceptance criteria cho tính năng "khóa tài khoản" hay "đăng xuất khỏi mọi thiết bị", bạn KHÔNG được mặc định rằng nó có hiệu lực tức thì. Hãy viết rõ độ trễ chấp nhận được, ví dụ: "Sau khi admin khóa tài khoản, người dùng phải mất quyền truy cập trong vòng tối đa 15 phút." Con số 15 phút này là một quyết định nghiệp vụ, không phải kỹ thuật thuần túy.

Tình huống 2 — Fintech và rủi ro để dữ liệu nhạy cảm trong Payload

Một công ty fintech cho vay tiêu dùng ở Hà Nội, tạm gọi là VayNhanh, trong giai đoạn đầu đã nhét khá nhiều thông tin vào Payload của JWT cho tiện: số CCCD, hạn mức vay, và cả điểm tín dụng nội bộ của khách hàng. Lập trình viên nghĩ rằng vì token "đã được mã hóa" nên an toàn.

Trong một đợt rà soát bảo mật (security audit) trước vòng gọi vốn, chuyên gia đã dán một token mẫu vào jwt.io và đọc ra toàn bộ thông tin trên trong vài giây. Đây là lỗ hổng nghiêm trọng vì token được lưu ở localStorage của trình duyệt, nơi mã JavaScript độc hại (qua tấn công XSS) có thể đánh cắp.

Diễn giải: Team buộc phải thiết kế lại: Payload chỉ giữ sub (user ID) và role. Mọi thông tin nhạy cảm được lấy từ database khi cần, dựa trên user ID. Điểm tín dụng không bao giờ rời khỏi server.

Bài học cho BA: Khi review spec về xác thực, hãy luôn đặt câu hỏi: "Payload của token chứa những trường gì? Có trường nào nhạy cảm không?" Một BA hiểu rằng Base64 ≠ mã hóa bảo mật sẽ ngăn được những lỗ hổng tốn kém trước khi chúng lên production.

Tình huống 3 — Super app và Single Sign-On giữa nhiều dịch vụ

Một super app gọi xe + giao đồ ăn + ví điện tử kiểu Grab/Gojek vận hành hàng chục microservice riêng biệt (đặt xe, thanh toán, khuyến mãi...). Họ cần người dùng đăng nhập một lần và dùng được tất cả dịch vụ.

JWT giải bài toán này rất đẹp. Một service trung tâm (Auth Service) phát hành JWT đã ký bằng RS256 — thuật toán bất đối xứng dùng cặp khóa private/public. Auth Service ký bằng private key. Mỗi microservice khác chỉ cần giữ public key để verify token, không cần gọi ngược về Auth Service hay tra database. Một token, mọi service đều tin.

Diễn giải: Điểm tinh tế ở đây là việc chọn RS256 thay vì HS256. Với HS256, mọi service đều phải biết cùng một secret key — rủi ro lộ khóa rất cao khi có hàng chục service. Với RS256, chỉ Auth Service giữ private key (khóa ký), các service còn lại chỉ cần public key (khóa kiểm tra) — kể cả lộ public key cũng không thể giả mạo token.

Bài học cho BA: Trong các hệ kiến trúc nhiều dịch vụ, JWT là chất keo gắn kết xác thực. Khi bạn nghe team tranh luận "HS256 hay RS256", giờ bạn hiểu đó là câu chuyện về việc bao nhiêu bên cần ký và bao nhiêu bên chỉ cần kiểm tra.

Hướng dẫn từng bước

Đây là cách một BA tiếp cận một tính năng có dùng JWT, từ góc độ phân tích nghiệp vụ:

Bước 1 — Tự "giải mã" một token để hiểu. Lấy một token mẫu từ môi trường test (xin developer), vào trang jwt.io, dán vào. Quan sát ba phần Header/Payload/Signature hiện ra. Việc này giúp bạn thấy tận mắt những claim mà hệ thống của bạn đang dùng.

Bước 2 — Lập danh sách claim cần thiết. Cùng team xác định Payload nên chứa gì: chắc chắn có sub (user ID), thường có role/permissions, exp. Với mỗi claim, hỏi: "Trường này có nhạy cảm không? Nếu lộ ra ngoài có sao không?". Loại bỏ mọi thứ nhạy cảm.

Bước 3 — Quyết định thời hạn token (exp). Đây là quyết định nghiệp vụ. Token ngắn (15 phút) thì an toàn hơn nhưng cần refresh token để người dùng không phải đăng nhập liên tục. Token dài (vài ngày) thì tiện nhưng rủi ro cao khi bị đánh cắp. Cân bằng dựa trên độ nhạy cảm của sản phẩm — ngân hàng khác với app đọc tin tức.

Bước 4 — Thiết kế cơ chế refresh và logout. Viết rõ trong spec: access token ngắn hạn + refresh token dài hạn. Khi nào refresh? Khi access token hết hạn. Logout làm gì? Xóa token ở client và thu hồi refresh token ở server.

Bước 5 — Viết acceptance criteria bao phủ các trạng thái token. Tối thiểu cần phủ: token hợp lệ → cho vào; token hết hạn → trả lỗi 401 và yêu cầu đăng nhập lại; token bị sửa/giả → từ chối; không có token → 401. (Liên hệ Bài 9 về status codes — 401 Unauthorized là mã bạn sẽ gặp nhiều nhất ở đây.)

Lỗi thường gặp & mẹo

Lỗi 1 — Nhầm "decode" với "bảo mật". Rất nhiều người (kể cả developer mới) tưởng nội dung JWT được mã hóa bí mật. Sai. Payload ai cũng đọc được. Chữ ký chỉ chống sửa đổi, không chống đọc. Mẹo: hãy ghi nhớ câu "JWT chống giả mạo, không chống tò mò".

Lỗi 2 — Để token sống quá lâu. Một token 30 ngày bị đánh cắp = kẻ tấn công có 30 ngày tự do. Mẹo: dùng access token ngắn (phút) + refresh token dài, thay vì một token dài duy nhất.

Lỗi 3 — Kỳ vọng logout có hiệu lực tức thì. Như tình huống ShopViet, vì stateless nên không dễ thu hồi. Mẹo: khi viết yêu cầu logout/khóa tài khoản, luôn ghi rõ độ trễ chấp nhận được, hoặc yêu cầu thêm cơ chế blacklist token nếu nghiệp vụ đòi hỏi thu hồi tức thì (đánh đổi: server phải lưu danh sách đen, mất một phần lợi thế stateless).

Lỗi 4 — Lưu token sai chỗ. localStorage tiện nhưng dễ bị XSS đánh cắp; cookie có cờ HttpOnly an toàn hơn trước XSS nhưng cần phòng CSRF. Mẹo: BA không cần quyết định kỹ thuật này, nhưng nên đặt câu hỏi "token lưu ở đâu và rủi ro tương ứng là gì" trong buổi review bảo mật.

Mẹo vàng: Phân biệt rõ ba khái niệm khi nói chuyện với team — authentication (anh là ai, JWT giúp việc này), authorization (anh được làm gì, dựa trên claim role trong token), và session management (quản lý vòng đời phiên). JWT chạm vào cả ba nhưng theo những cách khác nhau.

Bài tập thực hành

  • Giải mã thủ công: Vào jwt.io, dán token mẫu sau và liệt kê tất cả các claim trong Payload, kèm ý nghĩa từng claim: eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI3ODkiLCJyb2xlIjoiZWRpdG9yIiwiZXhwIjoxNzE5NDg4ODAwfQ.abc123. Sau đó tự kiểm tra: nếu bạn đổi role thành admin, điều gì sẽ xảy ra khi gửi lên server?
  • Viết acceptance criteria: Cho tính năng "Đăng nhập" của một app ngân hàng số. Viết tối thiểu 5 acceptance criteria bao phủ: đăng nhập thành công, token hết hạn, token bị giả mạo, đăng xuất, và khóa tài khoản từ phía admin. Ghi rõ độ trễ chấp nhận được cho việc khóa tài khoản.
  • Tình huống quyết định: Sản phẩm của bạn là một app đọc báo miễn phí, người dùng ngại đăng nhập lại. Bạn sẽ đề xuất exp bao lâu và vì sao? So sánh với trường hợp đó là app chuyển tiền. Viết một đoạn ngắn lý giải sự khác biệt dựa trên đánh đổi giữa bảo mật và trải nghiệm.
  • Phát hiện rủi ro: Đọc đoạn mô tả Payload sau và chỉ ra (các) vấn đề: { "sub": "555", "email": "a@gmail.com", "password": "12345", "card_number": "9704...", "role": "user" }. Bạn sẽ đề xuất sửa thế nào?

Tóm tắt

JWT (JSON Web Token) là một dạng token tự đóng gói: thông tin về người dùng nằm sẵn bên trong token, nên server có thể xác thực mà không cần tra cứu database — đó là tính chất stateless. Cấu trúc gồm ba phần ngăn cách bởi dấu chấm: Header (loại token + thuật toán), Payload (các claim như sub, role, exp), và Signature (chữ ký chống giả mạo, tạo bằng secret key mà chỉ server biết).

Điểm quan trọng nhất cần khắc cốt ghi tâm: Header và Payload chỉ được mã hóa Base64URL chứ không phải mã hóa bảo mật — ai cũng đọc được, nên đừng bao giờ để dữ liệu nhạy cảm vào Payload. Chữ ký chỉ chống sửa đổi, không chống đọc.

Với vai trò BA, ba câu hỏi bạn nên luôn mang theo khi gặp JWT trong một spec là: Payload chứa gì (có nhạy cảm không)? Token sống bao lâu (exp)? Và làm sao thu hồi khi cần (logout, khóa tài khoản)? Trả lời được ba câu này, bạn đã biến một khái niệm tưởng chừng thuần kỹ thuật thành những quyết định nghiệp vụ rõ ràng — và đó chính là giá trị của một technical BA giỏi. Ở Bài 19 tiếp theo, ta sẽ đi sâu vào việc quản lý API Key: tạo, xoay vòng (rotate) và thu hồi (revoke).