Trong các ứng dụng web hiện đại, việc xác thực và ủy quyền người dùng là yếu tố quan trọng để bảo đảm an ninh. JWT (JSON Web Token) đã trở thành một trong những công nghệ phổ biến nhất để xử lý xác thực, nhờ tính gọn nhẹ và khả năng hoạt động tốt trong các hệ thống phân tán. Bài viết này sẽ giải thích chi tiết và tỉ mỉ về cách JWT hoạt động trong quy trình xác thực người dùng, từ việc đăng nhập, xử lý yêu cầu đến làm mới token.
JWT là viết tắt của JSON Web Token, một tiêu chuẩn mở (RFC 7519) để truyền tải dữ liệu an toàn giữa hai bên dưới dạng JSON. Token này được mã hóa và có thể xác thực tính toàn vẹn của nó thông qua chữ ký số. Trong một hệ thống xác thực, JWT được sử dụng để định danh và cấp quyền cho người dùng, giúp duy trì phiên làm việc mà không cần lưu trữ trạng thái trên máy chủ.
JWT có ba phần chính:
Một JWT thường có dạng như sau:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Dưới đây là luồng chi tiết về cách JWT hoạt động trong quá trình xác thực người dùng:
Khi người dùng nhập thông tin đăng nhập (như email và mật khẩu) và gửi chúng đến server qua HTTP POST, server sẽ kiểm tra thông tin đó.
Ví dụ:
POST /login Content-Type: application/json { "email": "[email protected]", "password": "password123" }
Server nhận thông tin và xác thực bằng cách so sánh với dữ liệu lưu trữ trong cơ sở dữ liệu. Nếu thông tin hợp lệ, server sẽ tạo một JWT và gửi lại cho người dùng.
JWT được tạo với một payload chứa các thông tin người dùng như userId
, roles
, và thời gian hiệu lực (exp
). Header của JWT xác định thuật toán mã hóa, thường là HMAC SHA256.
Ví dụ:
{ "sub": "1234567890", "name": "John Doe", "admin": true, "iat": 1516239022, "exp": 1516249022 }
Phần chữ ký (Signature) sẽ được mã hóa bằng cách sử dụng khóa bí mật để bảo đảm tính toàn vẹn của token.
JWT sau khi tạo xong sẽ được gửi trả lại cho client trong HTTP response. Người dùng sẽ lưu trữ JWT này để sử dụng cho các yêu cầu tiếp theo.
Ví dụ:
{ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." }
JWT có thể được lưu trữ ở nhiều vị trí trên client, tùy thuộc vào mục tiêu bảo mật và yêu cầu của ứng dụng:
HttpOnly
và Secure
.Ví dụ lưu trữ JWT trong LocalStorage:
localStorage.setItem("token", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...");
Trong các yêu cầu tiếp theo, client cần gửi JWT đã nhận để xác thực. Token này thường được gửi trong HTTP header Authorization
dưới dạng Bearer Token.
Ví dụ yêu cầu với JWT:
GET /protected-resource Host: api.example.com Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
JWT sẽ được gửi cùng mọi yêu cầu tới các API bảo mật. Server sau đó sẽ xác thực token này để cấp quyền truy cập.
Khi server nhận được yêu cầu có chứa JWT, nó sẽ xác thực chữ ký để đảm bảo rằng token chưa bị thay đổi. Việc xác thực dựa vào khóa bí mật đã được sử dụng để mã hóa JWT.
Nếu token hợp lệ, server sẽ giải mã payload và trích xuất thông tin người dùng từ đó. Thông tin này có thể bao gồm userId
, quyền hạn của người dùng (roles), và thời gian hết hạn của token (exp).
Server kiểm tra xem JWT có còn hợp lệ không dựa trên thuộc tính exp
. Nếu token đã hết hạn, server sẽ trả về lỗi yêu cầu đăng nhập lại (thường là 401 Unauthorized).
Ví dụ kiểm tra token trong ứng dụng Node.js:
const jwt = require('jsonwebtoken'); function authenticateToken(req, res, next) { const token = req.header('Authorization').split(' ')[1]; if (!token) return res.sendStatus(401); jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, user) => { if (err) return res.sendStatus(403); req.user = user; next(); }); }
Khi xác thực thành công JWT, server có thể sử dụng thông tin từ payload để xác định quyền truy cập. Nếu người dùng có quyền hợp lệ, server sẽ cho phép truy cập tài nguyên. Nếu không, server trả về mã lỗi như 403 Forbidden.
Ví dụ kiểm tra quyền trong hệ thống:
function authorizeAdmin(req, res, next) { if (!req.user.admin) return res.sendStatus(403); next(); }
JWT thường có thời gian sống ngắn, do đó để tránh việc người dùng phải đăng nhập lại liên tục, ta sử dụng Refresh Token. Refresh Token có thời gian sống lâu hơn JWT và được sử dụng để cấp mới JWT khi nó hết hạn.
Ví dụ luồng xử lý làm mới token:
app.post('/token', (req, res) => { const refreshToken = req.body.token; if (!refreshToken) return res.sendStatus(401); if (!refreshTokens.includes(refreshToken)) return res.sendStatus(403); jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET, (err, user) => { if (err) return res.sendStatus(403); const accessToken = generateAccessToken({ name: user.name }); res.json({ accessToken }); }); });
Lưu ý: Refresh Token thường được lưu trữ trong Cookies để bảo đảm an toàn.
JWT cung cấp một phương pháp xác thực an toàn, không trạng thái và rất hiệu quả cho các ứng dụng web hiện đại. Tuy nhiên, việc bảo mật JWT rất quan trọng, và bạn cần cân nhắc việc bảo vệ token trước các cuộc tấn công tiềm ẩn như XSS, CSRF. Trong quá trình phát triển, hãy luôn kiểm tra thời hạn và quyền hạn của token để bảo đảm rằng hệ thống của bạn luôn an toàn.