Một trong những trải nghiệm ức chế nhất của người dùng: Đang điền form dài dằng dặc, bấm Submit -> "Phiên đăng nhập hết hạn" -> Mất sạch dữ liệu.
Cơ chế Dual Token (Access + Refresh) sinh ra để giải quyết vấn đề này, đồng thời đảm bảo bảo mật.
1. Tại sao cần 2 Token?
Nếu chỉ dùng 1 Token sống 30 ngày:
- Tiện: User sướng.
- Nguy hiểm: Hacker trộm được Token -> Nó có quyền truy cập tài khoản của bạn trong 30 ngày! Bạn đổi mật khẩu cũng vô tác dụng (với JWT stateless).
Giải pháp Dual Token:
- Access Token (Chìa khóa phòng):
- Sống rất ngắn (15 phút).
- Dùng để gọi API lấy dữ liệu.
- Refresh Token (Thẻ cư dân):
- Sống rất dài (7-30 ngày).
- Chỉ dùng để xin Access Token mới khi cái cũ hết hạn.
- Được lưu bảo mật (HttpOnly Cookie) để JS không trộm được.
2. Token Rotation Flow (Vòng đời)
- Login: Server trả về
Access Token+Refresh Token(HttpOnly Cookie). - Request API: Client gửi
Access Token. - Token Expired: Sau 15p, Client gửi
Access Token-> Server trả về lỗi401 Unauthorized. - Silent Refresh:
- Client bắt được lỗi 401.
- Client ngầm gọi API
/auth/refreshgửi kèmRefresh Token. - Server check
Refresh Tokenhợp lệ -> Trả vềAccess TokenMỚI. - Client lưu
Access Tokenmới và thử lại request ban đầu. - User KHÔNG HỀ HAY BIẾT gì cả (Trải nghiệm mượt mà).
3. Implementation: Axios Interceptor
Đừng gọi API refresh thủ công. Hãy dùng Interceptor để tự động hóa.
ts:
import axios from "axios";
const api = axios.create({ baseURL: "/api" });
api.interceptors.response.use(
(response) => response, // Thành công -> Trả về luôn
async (error) => {
const originalRequest = error.config;
// Nếu lỗi 401 và chưa thử refresh lần nào
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true; // Đánh dấu để tránh lặp vô tận
try {
// Gọi API lấy token mới
const res = await axios.post("/api/auth/refresh");
const newAccessToken = res.data.accessToken;
// Cập nhật token cho request hiện tại và các request sau
api.defaults.headers.common["Authorization"] = "Bearer " + newAccessToken;
originalRequest.headers["Authorization"] = "Bearer " + newAccessToken;
// Thử lại request ban đầu
return api(originalRequest);
} catch (refreshError) {
// Refresh cũng lỗi (Hết hạn hẳn) -> Redirect về Login
window.location.href = "/login";
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);4. Bảo mật Refresh Token
Refresh Token quyền lực như vậy, lưu ở đâu?
- ❌ LocalStorage: KHÔNG! Hacker dùng XSS (chèn script độc hại) là trộm được ngay.
- ✅ HttpOnly Cookie: Cookie này chỉ trình duyệt đọc được và gửi đi, JavaScript (và Hacker) không thể
document.cookieđể xem. Đây là cách an toàn nhất cho Web App.
Kết luận
Cơ chế Access/Refresh Token là sự cân bằng hoàn hảo giữa Bảo mật (Token ngắn hạn) và Trải nghiệm (Token dài hạn). Hãy triển khai nó ngay nếu bạn không muốn user chửi thề vì bị logout giữa chừng.