Mục lục

Bí mật Access Token & Refresh Token

Tại sao cần 2 loại Token? Cơ chế tự động gia hạn phiên đăng nhập (Silent Refresh) giúp User không bị đá ra ngoài.

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:

  1. 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.
  2. 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)

  1. Login: Server trả về Access Token + Refresh Token (HttpOnly Cookie).
  2. Request API: Client gửi Access Token.
  3. Token Expired: Sau 15p, Client gửi Access Token -> Server trả về lỗi 401 Unauthorized.
  4. Silent Refresh:
    • Client bắt được lỗi 401.
    • Client ngầm gọi API /auth/refresh gửi kèm Refresh Token.
    • Server check Refresh Token hợp lệ -> Trả về Access Token MỚI.
    • Client lưu Access Token mớ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.

Quảng cáo
mdhorizontal