JWT là gì: cấu trúc token và cách decode an toàn
19/07/2026
Nếu bạn từng làm việc với API, đăng nhập hay xác thực trong ứng dụng web, gần như chắc chắn bạn đã gặp JWT. Đây là một chuỗi ký tự dài, trông có vẻ khó hiểu, thường xuất hiện trong header Authorization: Bearer ... hoặc lưu trong cookie. Bài viết này giải thích JWT là gì, cấu trúc thật sự của nó, và quan trọng nhất: sự khác biệt giữa decode và verify — hiểu sai điểm này là nguồn gốc của rất nhiều lỗ hổng bảo mật.
JWT là gì?
JWT (JSON Web Token) là một chuẩn mở (RFC 7519) để truyền thông tin giữa các bên dưới dạng một đối tượng JSON được ký số. Điểm mấu chốt: token được ký, nghĩa là bên nhận có thể kiểm tra thông tin bên trong có bị chỉnh sửa hay không, nhưng bản thân nội dung không được mã hoá.
Công dụng phổ biến nhất của JWT là xác thực stateless. Trong mô hình truyền thống, server phải lưu session của từng người dùng trong bộ nhớ hoặc database, và mỗi request phải tra cứu lại. Với JWT, server ký một token chứa thông tin người dùng (ví dụ user ID, role) và gửi cho client. Ở các request sau, client gửi kèm token; server chỉ cần xác minh chữ ký là biết token hợp lệ mà không cần tra database. Điều này giúp hệ thống dễ mở rộng theo chiều ngang (nhiều server không cần chia sẻ bộ nhớ session).
Ngoài xác thực, JWT còn dùng để truyền thông tin có đảm bảo tính toàn vẹn: ví dụ một dịch vụ A phát hành token cho dịch vụ B, B tin tưởng dữ liệu vì nó được A ký.
Cấu trúc 3 phần của JWT
Một JWT gồm ba phần, ngăn cách bởi dấu chấm (.):
Header.Payload.Signature
Mỗi phần được mã hoá bằng base64url (một biến thể của base64 an toàn cho URL, dùng - và _ thay cho + và /, bỏ ký tự đệm =). Lưu ý: base64url chỉ là mã hoá để biểu diễn, không phải mã hoá bảo mật — bất kỳ ai cũng có thể giải ngược lại.
Header
Phần đầu mô tả cách token được ký. Hai trường thường gặp:
- alg — thuật toán ký, ví dụ
HS256(HMAC với SHA-256, dùng secret đối xứng) hoặcRS256(RSA với SHA-256, dùng cặp khoá công khai/bí mật). - typ — loại token, thường là
JWT.
{
"alg": "HS256",
"typ": "JWT"
}
Payload
Phần thân chứa các claim — những mẩu thông tin về một thực thể (thường là người dùng) và metadata. Chuẩn JWT định nghĩa một số claim phổ biến (registered claims):
| Claim | Ý nghĩa |
|---|---|
| iss (issuer) | Bên phát hành token |
| sub (subject) | Chủ thể của token, thường là user ID |
| aud (audience) | Bên nhận token dự kiến |
| exp (expiration) | Thời điểm hết hạn (Unix timestamp) |
| iat (issued at) | Thời điểm phát hành |
| nbf (not before) | Token chưa hợp lệ trước thời điểm này |
Ngoài ra bạn có thể thêm custom claim tùy ý, ví dụ role, email, tenant_id. Nhưng hãy nhớ nguyên tắc quan trọng ở phần sau: payload ai cũng đọc được.
Signature
Chữ ký đảm bảo header và payload không bị thay đổi. Với HS256, chữ ký được tạo như sau:
HMACSHA256(
base64url(header) + "." + base64url(payload),
secret
)
Với RS256, thay vì HMAC dùng secret chung, server ký bằng khoá riêng (private key) và bất kỳ ai cũng xác minh được bằng khoá công khai (public key). RS256 phù hợp khi nhiều dịch vụ cần xác minh token nhưng chỉ một bên được phép phát hành.
Decode KHÁC với verify — điểm quan trọng nhất
Đây là hiểu lầm nguy hiểm nhất về JWT. Cần phân biệt rõ:
- Decode: chỉ đơn giản là giải base64url header và payload để đọc nội dung JSON. Bất kỳ ai cầm được token đều decode được — không cần secret, không cần khoá. Payload không hề được mã hoá.
- Verify: kiểm tra chữ ký bằng secret (HS256) hoặc public key (RS256) để chứng minh token thật sự do server tin cậy phát hành và chưa bị sửa. Chỉ bên có khoá mới verify được.
Hệ quả trực tiếp: TUYỆT ĐỐI không đặt dữ liệu nhạy cảm vào payload — không mật khẩu, không số thẻ, không API key, không thông tin cá nhân nhạy cảm. Vì bất kỳ ai chặn được token (hoặc lấy từ localStorage/log) đều đọc sạch payload. Chữ ký chỉ chống sửa đổi, không chống đọc trộm.
Bạn có thể tự kiểm chứng ngay bằng công cụ JWT Decoder: dán một token vào và xem toàn bộ header + payload hiện ra tức thì mà không cần secret. Công cụ này chạy hoàn toàn phía trình duyệt (client-side) — token của bạn không được gửi lên bất kỳ server nào, nên an toàn để dùng cả với token thật khi debug.
Các lỗi bảo mật phổ biến
- Chấp nhận alg=none: một số thư viện cũ cho phép
alg: "none"nghĩa là token không cần chữ ký. Kẻ tấn công chỉ cần đặtalgthànhnone, xoá chữ ký, và tự do sửa payload. Luôn cấu hình danh sách thuật toán cho phép ở phía verify. - Không verify chữ ký: chỉ decode payload rồi tin ngay là thảm hoạ — vì payload có thể bị làm giả. Luôn gọi hàm verify với đúng khoá trước khi tin bất kỳ claim nào.
- Không kiểm tra exp: nếu bỏ qua thời điểm hết hạn, một token bị lộ sẽ dùng được mãi mãi. Luôn kiểm
exp(vànbfnếu có). - Secret yếu: với HS256, một secret ngắn hoặc dễ đoán có thể bị brute-force để giả mạo token. Dùng secret ngẫu nhiên đủ dài (tối thiểu 256 bit) và không commit vào mã nguồn.
- Nhầm lẫn RS256/HS256: một tấn công kinh điển là đổi
RS256thànhHS256rồi dùng public key làm secret HMAC. Cấu hình verify phải cố định thuật toán mong đợi.
Access token và refresh token
Trong thực tế người ta thường dùng hai loại token kết hợp:
- Access token: thường là JWT, sống ngắn (vài phút đến ~1 giờ). Gửi kèm mỗi request để truy cập tài nguyên. Ngắn hạn để giảm thiệt hại nếu bị lộ.
- Refresh token: sống dài hơn (nhiều ngày), dùng để xin access token mới khi cái cũ hết hạn, mà không bắt người dùng đăng nhập lại. Refresh token nên lưu an toàn (cookie HttpOnly) và có thể thu hồi phía server.
Mô hình này cân bằng giữa trải nghiệm (không phải đăng nhập liên tục) và bảo mật (access token lộ ra cũng nhanh hết hạn).
Ví dụ một JWT thật
Dưới đây là một token mẫu (đã xuống dòng cho dễ đọc, thực tế là một chuỗi liền):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Ba phần ngăn bởi dấu chấm. Giải base64url phần đầu (header) được:
{
"alg": "HS256",
"typ": "JWT"
}
Giải phần thứ hai (payload) được:
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
Bạn thấy đấy — chỉ cần base64url decode là đọc được toàn bộ, không cần secret. Phần thứ ba (SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c) là chữ ký; muốn xác minh nó hợp lệ, server phải dùng secret của mình để tính lại và so sánh.
Tóm tắt
JWT là công cụ mạnh cho xác thực stateless, nhưng dùng đúng đòi hỏi hiểu bản chất:
- JWT gồm ba phần base64url: Header.Payload.Signature.
- Payload không mã hoá — ai cũng decode đọc được, đừng bao giờ để dữ liệu nhạy cảm trong đó.
- Decode chỉ là đọc; verify mới chứng minh token đáng tin. Luôn verify chữ ký, kiểm
exp, cố định thuật toán, và dùng secret mạnh. - Kết hợp access token ngắn hạn với refresh token dài hạn để cân bằng bảo mật và trải nghiệm.
Muốn xem nhanh bên trong một token bất kỳ? Dùng ngay JWT Decoder của DevToolHub — nó decode header và payload ngay trên trình duyệt, không gửi token lên server, nên bạn có thể yên tâm dán cả token thật để debug. Hãy thử dán token của chính bạn và quan sát từng claim để nắm chắc kiến thức trong bài này.