0

series Axios Bài 3: Axios Interceptors - Bộ Đánh Chặn "Quyền Lực" Và Chiến Lược Tự Động Refresh Token

Hãy tưởng tượng Axios Interceptors giống như một trạm kiểm soát cửa khẩu (Customs Checkpoint) đặt ngay tại biên giới.

  • Khi một gói tin chuẩn bị rời khỏi máy của bạn để đi ra Internet, nó phải bước qua trạm Request Interceptor. Tại đây, bạn có thể kiểm tra hộ chiếu, đóng dấu, hoặc nhét thêm Token bảo mật vào gói tin.
  • Khi một gói tin từ Server phản hồi về tới máy của bạn, trước khi nó được chuyển tới các hàm then/catch trong code tính năng, nó phải bước qua trạm Response Interceptor. Tại đây, bạn có thể kiểm tra xem Server có trả về lỗi xác thực hay không để tìm cách xử lý kịp thời.

Hôm nay, chúng ta sẽ nâng cấp file axiosClient.js từ Bài 2 thành một bộ máy tự động hóa hoàn chỉnh.

1. Request Interceptor: Tự Động Đính Kèm Access Token

Thay vì việc mỗi lần gọi API bạn lại phải lấy Token từ localStorage ra và nhét vào header, hãy để Request Interceptor làm việc đó một cách tự động cho toàn bộ dự án.

Mở file src/api/axiosClient.js và chèn thêm đoạn code sau trước lệnh export default:

// Bộ đánh chặn Request (Trước khi gửi gói tin đi)
axiosClient.interceptors.request.use(
  (config) => {
    // Lấy token từ nơi lưu trữ (localStorage, sessionStorage, hoặc Redux store)
    const token = localStorage.getItem('access_token');
    
    // Nếu tồn tại token, tiến hành đính kèm vào Header Authorization
    if (token) {
      config.headers['Authorization'] = `Bearer ${token}`;
    }
    
    return config;
  },
  (error) => {
    // Xử lý lỗi xảy ra trước khi request được gửi đi
    return Promise.reject(error);
  }
);

Từ bây giờ, bất kỳ khi nào bạn gọi userApi.getAll(), Axios sẽ tự động chạy qua bộ lọc này, lôi token ra và đính vào header. Code tính năng của bạn hoàn toàn "sạch bóng" các logic về token.

2. Response Interceptor: Xử Lý Lỗi Tập Trung Và Tuyệt Chiêu Refresh Token

Đây mới là nơi phân cấp trình độ giữa các kỹ sư. Trong kiến trúc bảo mật Token hiện đại (như JWT), Access Token thường có vòng đời rất ngắn (ví dụ: 15 phút) để đảm bảo an toàn. Khi nó hết hạn, Server sẽ trả về lỗi 401 Unauthorized.

Chiến lược thông minh ở đây là: Khi nhận được lỗi 401 ở Response Interceptor, chúng ta sẽ tạm dừng gói tin bị lỗi đó lại, âm thầm gọi một API khác để lấy Access Token mới (Refresh Token API), sau đó dùng Token mới này để gửi lại chính cái request bị lỗi lúc nãy. Người dùng cuối sẽ hoàn toàn không nhận ra là token vừa bị hết hạn vì trải nghiệm của họ không hề bị ngắt quãng.

Hãy bổ sung tiếp đoạn code Response Interceptor dưới đây vào axiosClient.js:

// Bộ đánh chặn Response (Sau khi nhận kết quả từ Server về)
axiosClient.interceptors.response.use(
  (response) => {
    // Nếu request thành công, chỉ trả về tầng data để code ngắn gọn hơn
    return response.data;
  },
  async (error) => {
    const originalRequest = error.config;

    // Kiểm tra nếu Server trả về lỗi 401 (Hết hạn token) và request này chưa từng được thử lại
    if (error.response && error.response.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true; // Đánh dấu request này đã được thử lại để tránh vòng lặp vô hạn

      try {
        // 1. Gọi API Refresh Token để lấy Access Token mới
        const refreshToken = localStorage.getItem('refresh_token');
        
        // Lưu ý: Phải gọi qua instance axios gốc, KHÔNG dùng axiosClient ở đây để tránh bị lặp interceptor
        const res = await axios.post('https://api.myservice.com/v1/auth/refresh', {
          refresh_token: refreshToken
        });

        const newAccessToken = res.data.access_token;

        // 2. Lưu Access Token mới vào localStorage
        localStorage.setItem('access_token', newAccessToken);

        // 3. Cập nhật lại Header Authorization của request cũ bằng token mới
        originalRequest.headers['Authorization'] = `Bearer ${newAccessToken}`;

        // 4. Thực thi lại chính request cũ với token mới và trả về kết quả
        return axiosClient(originalRequest);
      } catch (refreshError) {
        // Nếu ngay cả việc refresh token cũng lỗi (ví dụ: Refresh Token cũng hết hạn)
        // Tiến hành xóa sạch token và đá người dùng về trang Login
        localStorage.removeItem('access_token');
        localStorage.removeItem('refresh_token');
        window.location.href = '/login';
        return Promise.reject(refreshError);
      }
    }

    // Đối với các lỗi khác (403, 404, 500...), ném thẳng về block catch của code tính năng
    return Promise.reject(error);
  }
);

3. Những Lưu Ý "Xương Máu" Khi Dùng Interceptors

  • Tránh bẫy vô hạn vòng lặp (Infinite Loop): Khi gọi API để Refresh Token bên trong Response Interceptor, bạn tuyệt đối phải dùng một thực thể axios độc lập hoặc cấu hình bỏ qua interceptor. Nếu bạn tiếp tục dùng axiosClient để gọi API refresh, khi API refresh đó bị lỗi 401, nó lại kích hoạt chính cái Response Interceptor này, tạo ra một vòng lặp vô hạn làm sập ứng dụng.
  • Đơn giản hóa dữ liệu trả về: Bạn có để ý dòng return response.data; ở trên không? Mặc định Axios trả về một object rất cồng kềnh gồm status, headers, config, data. Bằng cách bóc tách chỉ trả về response.data tại Interceptor, ở file tính năng bạn chỉ cần viết const users = await userApi.getAll() thay vì phải .data thêm một lần nữa.

Tóm lại là...

Làm chủ được Axios Interceptors giúp bạn xây dựng được một hệ thống quản lý session người dùng cực kỳ mượt mà và an toàn. Bạn tập trung toàn bộ các logic phức tạp về bảo mật mạng vào đúng một file duy nhất, giúp giao diện người dùng luôn hoạt động trơn tru bất kể sự biến động của Token phía sau hậu trường.

Ở bài học số 4, chúng ta sẽ giải quyết một bài toán tối ưu hiệu năng đường truyền: Cách sử dụng Axios CancelToken (hoặc AbortSignal) để hủy bỏ các request trùng lặp hoặc request thừa khi người dùng chuyển trang liên tục, giúp tiết kiệm tối đa tài nguyên cho cả Client lẫn Server.


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.