Verify đúng nghĩa là kiểm chữ ký, rồi kiểm các claim, và không để token tự quyết định cách nó được kiểm.
Các lỗi kinh điển:
1. alg: none — token khai báo không có chữ ký, thư viện cũ chấp nhận luôn. Kẻ tấn công tự chế payload tùy ý.
2. Nhầm lẫn thuật toán (RS256 → HS256) — server dùng RSA, kẻ tấn công đổi header sang HS256 rồi ký bằng public key (thứ ai cũng có) như thể đó là secret. Nếu code gọi verify(token, key) mà lấy alg từ chính header thì lọt.
3. Chỉ decode mà không verify — jwt.decode() không kiểm chữ ký; đây là lỗi rất hay gặp trong code thật.
4. Bỏ qua exp, aud, iss — token của môi trường khác hoặc của app khác cùng issuer vẫn dùng được.
5. Secret yếu cho HS256 — có thể brute-force offline vì token đã nằm trong tay kẻ tấn công.
// pin the algorithm explicitly, never read it from the token header
jwt.verify(token, publicKey, {
algorithms: ['RS256'],
issuer: 'https://auth.example.com',
audience: 'api://orders',
})Nguyên tắc chốt: thuật toán là cấu hình của server, không phải dữ liệu của token.
Và mặc định nên dùng thư viện đã được kiểm chứng thay vì tự viết phần verify.