Codestus.com

Go back

Refresh token trong Axios như thế nào?

Published at: 01/04/2023

7 mins read

Việc triển khai refresh token có thể không còn xa lạ đối với nhiều frontend dev trong chúng ta. Sau khi đăng nhập thành công, tokensẽ được trả lại từ API. Chúng ta sẽ lưu trữ nó lại ở localStorage, cookie, etc... để đính vào headers của mỗi request cho việc xác thực các request mà chúng ta gửi đi.

Khi token này hết hạn, chúng ta sẽ cần gửi một yêu cầu để lấy một token mới bằng việc gửi token hết hạn trước đó hoặc mã refersh token ban đầu được trả về từ API. Việc này phụ thuộc vào thiết kế API đó.

Trước khi biết khi nào token hết hạn, chúng ta cần đính token vào mỗi request gửi đi. Bạn có thể hình dung việc chúng ta đính token vào mỗi request như thế này, để đảm bảo chúng ta luôn gửi token đến API để xác thực và biết khi nào token sẽ hết hạn (Tránh tự ý kiểm tra token ở client, việc đó không thực sự an toàn).

axios.interceptors.request.use(
  function (request) {
    const token = getTokenInYourStorage();

    // Đính token vào header mới
    const newHeaders = {
      ...request.headers,
      Authorization: `Bearer ${token}`,
    };

    // Đính header mới vào lại request trước khi được gửi đi
    request = {
      ...request,
      headers: newHeaders,
    };

    return request;
  },
  function (error) {
    // Xử lý lỗi
    return Promise.reject(error);
  },
);

Khi mỗi request cần xác thực được gửi đi với token kèm theo, chúng ta sẽ biết token đó còn hợp lệ hay không bằng việc API sẽ giúp chúng ta xác minh token đó. khi token hết hạn, thông thường chúng ta sẽ nhận được mã phản hồi là 401 hoặc 403.

Khi đó, chúng ta sẽ kiểm tra trên mỗi response nhận về. Nếu nó rơi vào các mã lỗi trên, thực hiện kịch bản refersh token mà chúng ta muốn.

axios.interceptors.response.use(
  function (response) {
    // ...
    return request;
  },
  function(error) {
    const { response, config } = error;
    const status = response?.status;

    // Kiểm tra mã lỗi có phải là 401 hoặc 403 hay không
    if(status === 401 || status === 403) {
        // Chúng ta sẽ Thực hiện kịch bản refresh token tại đây
    }

    // Nếu không, trả lỗi về điểm cuối đã gọi api
    return Promise.reject(error);
);

Để refresh token, chúng ta sẽ cần có token cũ đã hết hạn trước đó, nếu không sẽ báo lỗi.

// ....
// 1. Should refresh token when status response 401
// if status is response code 401, we need to send request token here
if (!jwtService.getToken()) {
  return Promise.reject(error);
}
// ....

Tiếp theo, để đảm bảo chỉ một request refresh token được tạo ra khi nhiều request với token hết hạn phát sinh khi tải trang, chúng ta sẽ cần sử dụng Promise để delay và chỉ tạo duy nhất một yêu cầu cuối cùng. Kèm theo đó, là kiểm tra xem có request refresh token nào đã được gọi thông qua biến trạng thái isRefreshToken

// 1.1 refresh token is false, to call refresh token api
if (!isRefreshToken) {
  // @todo update status isRefresh to be true
  isRefreshToken = true;

  // 2. Once time to call refresh token api
  // @todo send request to refresh token here
  authService
    .refreshToken()
    .then(({ access_token }) => {
      requestsToRefresh.forEach((callback) => {
        callback(access_token);
      });

      // 4. Push access_token for callback in step 3
    })
    .catch((error) => {
      jwtService.removeToken();
      requestsToRefresh.forEach((cb) => cb(null));
    })
    .finally(() => {
      // 5. After that, to clear all setup
      isRefreshToken = false;
      requestsToRefresh = [];
    });
}

// 3. Setup callback to change token in headers authorization
return new Promise((resolve, reject) => {
  requestsToRefresh.push((token) => {
    if (token) {
      // Reset access_token for another request behind
      config.headers.Authorization = `Bearer ${token}`;
      resolve(instanceAxios(config));
    }

    reject(error);
  });
});

Ở đây, jwtService là service để tương tác get, set token với các storage. authService dùng để xử lý và tương tác với các API và ở trong trường hợp này chúng ta có phương thức refreshToken chỉ dùng để gọi API kèm theo token hết hạn của chúng ta trước đó.

Tóm gọn lại, chúng ta sẽ có một hàm refresh token như bên dưới.

let isRefreshToken = false;

axios.interceptors.response.use(
  function (response) {
    // ...
    return request;
  },
  (error) => {
    const { response, config } = error;
    const status = response?.status;

    // The account not active
    if (status === 406) {
      jwtService.removeToken();
      // return (window.location.href = "/login");
    }

    // 1. Should refresh token when status response 401
    // if status is response code 401, we need to send request token here
    if (status === 401) {
      if (!jwtService.getToken()) {
        return Promise.reject(error);
      }

      // 1.1 refresh token is false, to call refresh token api
      if (!isRefreshToken) {
        // @todo update status isRefresh to be true
        isRefreshToken = true;

        // 2. Once time to call refresh token api
        // @todo send request to refresh token here
        authService
          .refreshToken()
          .then(({ access_token }) => {
            requestsToRefresh.forEach((callback) => {
              callback(access_token);
            });

            // 4. Push access_token for callback in step 3
          })
          .catch((error) => {
            jwtService.removeToken();
            requestsToRefresh.forEach((cb) => cb(null));
          })
          .finally(() => {
            // 5. After that, to clear all setup
            isRefreshToken = false;
            requestsToRefresh = [];
          });
      }

      // 3. Setup callback to change token in headers authorization
      return new Promise((resolve, reject) => {
        requestsToRefresh.push((token) => {
          if (token) {
            // Reset access_token for another request behind
            config.headers.Authorization = `Bearer ${token}`;
            resolve(instanceAxios(config));
          }

          reject(error);
        });
      });
    }
    return Promise.reject(error);
  },
);

Kết luận

Có rất nhiều để chúng ta triển khai refresh token và phía trên là một trong số cách hiện tại mình đang sử dụng để refresh token.