JWT (JSON Web Token) là một chuỗi gồm ba phần base64url, header.payload.signature, được server cấp cho người dùng khi đăng nhập và kiểm tra ở mọi request sau đó. Chữ ký chứng minh rằng server đã cấp token và không ai sửa nó, nhưng payload chỉ được mã hóa base64url chứ không được mã hóa bảo mật, nên ai cũng đọc được. Trong Node.js, bạn tạo token bằng new SignJWT(payload).sign(secret) từ package jose và kiểm tra nó bằng jwtVerify(token, secret).
Chương trình in ra 3 parts, rồi user-42 admin: jwtVerify đã kiểm tra chữ ký và thời hạn rồi trả về payload. Sửa một ký tự trong khóa bí mật truyền vào jwtVerify rồi chạy lại: việc xác thực thất bại với signature verification failed.
Bên trong một JWT có gì
Header và payload là JSON, được mã hóa bằng base64url. Giải mã chúng không cần khóa, vì vậy JWT tuyệt đối không được chứa mật khẩu hay bất kỳ thông tin bí mật nào.
Hai dòng đầu in ra {"alg":"HS256"} và {"name":"Ada","role":"editor"}, ai có token cũng đọc được. Chữ ký (43 ký tự với HS256) là thứ ngăn họ chỉnh sửa token: đổi editor thành admin thì chữ ký không còn khớp.
| Phần | Chứa gì | Bí mật? |
|---|---|---|
| Header | Thuật toán, như HS256 | Không, đọc được |
| Payload | Các claim: sub (id người dùng), exp (hết hạn), iat (thời điểm cấp), các trường của riêng bạn | Không, đọc được |
| Signature | Chữ ký HMAC hoặc RSA của hai phần đầu | Cần khóa để tạo |
Thời hạn và xác thực thất bại
setExpirationTime('15m') ghi một claim exp, và jwtVerify từ chối token đã quá hạn này. Mỗi lần từ chối là một lỗi có code, nên bạn phân biệt được token hết hạn với token giả mạo.
Lần kiểm tra đầu thất bại với ERR_JWT_EXPIRED, còn lần thứ hai, với chữ ký đã bị sửa, thất bại với ERR_JWS_SIGNATURE_VERIFICATION_FAILED. Với API của bạn thì cả hai đều như nhau (trả về 401), nhưng ghi log mã lỗi sẽ giúp khi gỡ lỗi.
Bảo vệ route Express bằng Bearer token
Client đăng nhập một lần, giữ token và gửi nó trong header Authorization dưới dạng Bearer <token>. Một middleware xác thực token và gắn payload vào req.user cho route dùng.
Request đầu tiên, GET /me không có token, nhận 401 và Missing token. Gửi POST /login với body {"username": "ada", "password": "lovelace"}, sao chép token từ response, rồi gửi lại GET /me với header Authorization: Bearer <token>: câu trả lời là {"id":"user-1","name":"ada"}.
Package jsonwebtoken
jsonwebtoken (npm install jsonwebtoken) là lựa chọn phổ biến còn lại. Hàm sign và verify của nó chạy đồng bộ (có thể kèm callback) thay vì trả về promise:
const jwt = require('jsonwebtoken');
const token = jwt.sign({ role: 'admin' }, process.env.JWT_SECRET, {
subject: 'user-42',
expiresIn: '1h',
});
const payload = jwt.verify(token, process.env.JWT_SECRET); // throws if invalid
Hãy dùng jose cho code mới: nó không có dependency nào và còn chạy được trên edge runtime và trình duyệt. Token từ thư viện nào cũng là JWT chuẩn, nên thư viện này xác thực được token mà thư viện kia đã ký.
Lỗi thường gặp
Dùng decode thay vì verify. jwt.decode và decodeJwt đọc payload mà không kiểm tra chữ ký, nên token giả mạo vẫn lọt qua. Chỉ tin những gì verify trả về.
Khóa bí mật quá ngắn hoặc bị commit. Một khóa HS256 dễ đoán cho phép kẻ tấn công tự ký token. Hãy dùng ít nhất 32 byte ngẫu nhiên, đọc nó từ process.env, và không bao giờ commit nó.
Token không bao giờ hết hạn. Không có exp, một token bị lộ sẽ dùng được mãi mãi. Luôn đặt thời hạn.
Câu hỏi thường gặp
Client nên lưu JWT ở đâu?
Trong app trên trình duyệt, cookie HttpOnly, Secure là an toàn nhất, vì script trên trang không đọc được nó. localStorage đơn giản hơn nhưng mọi script trên trang, kể cả script bị chèn vào, đều đọc được token. App di động dùng kho lưu trữ bảo mật của nền tảng.
Làm sao đăng xuất người dùng khi dùng JWT?
Server không thể hủy một token đã ký; token vẫn hợp lệ cho đến khi hết hạn. Hãy xóa nó ở client, giữ access token có thời hạn ngắn, và nếu cần đăng xuất tức thì, hãy lưu danh sách id của các token đã thu hồi (jti) trên server rồi kiểm tra ở mọi request.
Nên chọn HS256 hay RS256?
HS256 dùng một khóa bí mật chung cho cả ký và xác thực, phù hợp với một backend duy nhất. RS256 ký bằng private key và xác thực bằng public key, nên các service khác có thể kiểm tra token mà không thể tạo token.
JWT nên có hiệu lực trong bao lâu?
Access token thường có hiệu lực từ 5 đến 60 phút. Với phiên dài hơn, hãy dùng kèm một refresh token được lưu cẩn thận hơn, để client đổi lấy access token mới.