Debug JWT: checklist decode header/payload khi API trả 401【2026】
Debug JWT = checklist có thứ tự, không phải ‘dán lên jwt.io rồi đoán’. Khi 401: đọc header (alg, kid) → payload (exp → sub/aud/roles) → mới nghi chữ ký. Decode chỉ cho bạn thấy claims; verify mới chứng minh token chưa bị sửa. Làm nhanh trên trình duyệt với JWT decode Kawa — không gửi token lên server.
Bạn đang tích hợp OAuth / API gateway / NestJS / Laravel Sanctum-style JWT. Postman gọi được vài phút trước, giờ frontend đỏ 401. Đồng nghiệp bảo “token sai”. Bạn mở DevTools, copy chuỗi eyJ…, rồi… làm gì tiếp theo theo thứ tự nào?
Bài này là playbook debug: decode header + payload theo checklist cố định — góc dành cho dev Việt Nam làm freelance/outsource hay nhảy giữa nhiều project auth khác nhau trong tuần.
Bài viết này giúp bạn
- Hiểu cấu trúc 3 phần JWT và vì sao payload không phải mã hóa
- Checklist đọc header rồi payload khi gặp 401 / sai quyền
- Bảng claim thường gặp và tín hiệu đỏ (
alg=none, aud lệch, exp đã qua) - Case study: token “nhìn đúng” nhưng API vẫn từ chối
- Dùng tool local thay vì paste production token lên site không rõ nguồn
JWT khi debug: bạn đang nhìn cái gì?
JWT (JSON Web Token) là chuỗi:
header.payload.signature
Ba phần ngăn bởi ., mỗi phần là Base64URL (không phải encryption):
| Phần | Mục đích khi debug | Bạn có đọc được không? |
|---|---|---|
| Header | Thuật toán, loại token, đôi khi kid | Có — decode ngay |
| Payload | Claims: sub, exp, aud, role… | Có — decode ngay |
| Signature | Chứng minh header+payload chưa bị sửa | Chỉ có ý nghĩa sau verify với key đúng |
Ai cầm token đều đọc được payload. Đó là lý do không đặt mật khẩu, số thẻ, API key production vào claims — kể cả khi “chỉ dùng nội bộ”.
// Ví dụ header sau decode
{
"alg": "RS256",
"typ": "JWT",
"kid": "auth-key-2026-03"
}
// Ví dụ payload sau decode
{
"sub": "user_9182",
"aud": "https://api.partner.vn",
"exp": 1784505600,
"roles": ["editor"]
}
Nhìn được JSON ≠ server chấp nhận token. Server còn kiểm tra chữ ký + policy claims.
Checklist debug theo thứ tự (in ra dán cạnh màn hình)
Lấy đúng chuỗi: bỏ tiền tố Bearer và khoảng trắng thừa; token phải có đúng 2 dấu chấm (3 phần)
Decode header trước: ghi alg, typ, kid — so với docs / JWKS của môi trường đang gọi
Decode payload: kiểm tra exp (và nbf nếu có) trước mọi thứ khác
Đối chiếu sub / aud / iss / scope|roles với API bạn đang hit
Xác nhận Authorization header trên request thật (Network tab), không nhầm access vs refresh
Claims ổn mà vẫn 401 → chuyển sang verify signature / đúng key / clock skew — không đoán mò trên frontend
Bước A — Header: 60 giây quyết định hướng
| Field | Ý nghĩa | Khi lệch thì sao |
|---|---|---|
| alg | HS256 / RS256 / ES256… | Server expect RS256 mà token HS256 → verify fail. alg=none = từ chối ngay |
| typ | Thường JWT | Một số gateway bắt đúng typ; hiếm nhưng dễ bỏ sót |
| kid | Key ID trong JWKS | kid cũ sau rotate → 401 dù payload đúng user |
| cty | Content type (hiếm) | Nested JWT — đừng decode một lớp rồi kết luận |
Ghi chú thực tế: team outsource hay nhận staging JWKS nhưng dán token production (hoặc ngược lại). kid không có trong JWKS → fail. Decode header giúp phát hiện trước khi soi Nest middleware 200 dòng.
Bước B — Payload: claim nào đọc trước?
| Claim | Nghĩa | Câu hỏi debug |
|---|---|---|
| exp | Hết hạn (Unix giây) | now >= exp? → refresh / login lại |
| nbf | Not before | Đồng hồ máy lệch tương lai? Token chưa có hiệu lực |
| iat | Issued at | Token 'già' bất thường — nghi copy nhầm / replay |
| sub | Subject / user id | Đúng user bạn nghĩ đang test không? |
| aud | Audience | Token cho API A gửi sang API B? |
| iss | Issuer | Issuer auth-dev vs auth-prod? |
| scope / roles | Quyền | 200 nhưng 403 sau đó — thiếu scope, không phải 'JWT hỏng' |
Mẹo chuyển exp: Unix giây → giờ đọc được bằng Unix timestamp hoặc new Date(exp * 1000) trong console. Đừng so với giờ máy đã set sai timezone mà quên UTC.
Decode ≠ verify (sai một bước = debug sai cả buổi)
| Thao tác | Bạn biết được gì | Bạn chưa biết gì |
|---|---|---|
| Decode | Nội dung header/payload | Token có bị sửa không; key có đúng không |
| Verify | Chữ ký khớp key → chưa bị giả mạo header/payload | Privileges business (vẫn cần check aud, revoke list…) |
Frontend decode để hiện tên user trong UI là bình thường. Tin roles: ["admin"] từ payload chưa verify để mở trang admin trên client là lỗ hổng — attacker tự sửa Base64 rồi gửi lại; không có signature hợp lệ thì backend phải từ chối, nhưng UI đã lộ.
Khi debug, hãy nói rõ với team: “Tôi đã decode — exp còn 12 phút, aud khớp. Chưa verify.” Tránh báo cáo “JWT OK” chỉ vì JSON đẹp.
Tool JWT decode trên Kawa xử lý local: dán token → xem header/payload JSON, hiện trạng thái exp. Có thêm bước xác minh HS256 nếu bạn có secret dev — không dán secret production lên bất kỳ trang web nào. RS256/JWKS vẫn nên verify trên server hoặc CLI nội bộ.
🔍 Giải mã JWT ngay tại đây
* Tự động giải mã khi nhập hoặc dán
Xác minh chữ ký (Signature Verification)
Case study 1: “Token mới lấy mà vẫn 401”
Bối cảnh: SPA React + API Nest. Login trả access token TTL 5 phút. Dev để tab mở, đi ăn trưa, quay lại bấm Save → 401.
Checklist áp dụng:
- Decode →
expđã qua 40 phút. - Refresh token endpoint trả 200 nhưng frontend vẫn gắn access cũ trong
axiosinterceptor (bug race). - Sửa: clear queue + gắn token mới trước retry; thêm log
exp - nowkhi 401.
Không cần đụng secret. Chỉ cần đọc exp đúng chỗ.
Case study 2: Payload đúng user, vẫn bị từ chối
Bối cảnh: Freelance nhận task gọi API đối tác. Token từ portal đối tác; sub đúng, exp còn hạn.
Checklist:
- Header:
alg: RS256,kid: partner-2025-01. - Docs mới: rotate sang
partner-2026-06. Portal vẫn cấp kid cũ trong 24h transition. - Gateway đã bỏ key cũ → verify fail → 401 generic.
Bài học: đọc kid trước khi nghi code của mình. Gửi screenshot header (đã che sub nếu cần) cho phía đối tác nhanh hơn đoán middleware.
Sai lầm hay gặp khi debug JWT
- Paste cả dòng
Bearer eyJ…vào tool → cấu trúc không còn 3 phần → lỗi decode. Chỉ lấy phần sauBearer. - Nhầm access token với id_token / refresh. Decode được nhưng
audkhác hẳn API resource. - Tin role trên UI. Payload nói
adminchưa verify → vẫn phải để backend quyết. - Screenshot token đầy đủ lên group chat. Coi như credential đã lộ — rotate nếu là token thật.
- Bỏ qua
nbfkhi đồng hồ VM/WSL lệch. Token “tương lai” vài phút → từ chối khó hiểu. - So
expbằng mili giây. Một số lib trả ms; JWT standard dùng giây — nhân nhầm ×1000 sẽ tưởng token sống tới năm 5000+.
Quy trình 5 phút trước khi @ backend
- Network: request có
Authorizationkhông? Status thật là 401 hay CORS giả dạng? - Decode header + payload trên công cụ JWT decode.
- Ghi vào ticket:
alg,kid,exp(ISO),aud,iss,sub(che một phần). - Nếu hết hạn → reproduce với token mới trước khi mở PR “fix auth”.
- Nếu claims khớp env → nhờ backend log lý do verify fail (signature / audience / revoke).
Câu hỏi thường gặp
401 thì kiểm tra gì trước?
Bearer đúng chưa → exp → sub/aud/roles → alg/kid → mới verify chữ ký.
Decode có cần secret?
Không. Secret chỉ cho verify.
exp là gì?
Unix giây hết hạn; so với UTC now.
Header xem field nào?
alg, typ, kid — đặc biệt sau key rotation.
Debug an toàn thế nào?
Tool local trong trình duyệt; che PII; không dán token production lên dịch vụ lạ.
Frontend verify được không?
Không lấy verify client làm lớp bảo mật. Decode để UX; ủy quyền ở server.