뒤로 가기

동시 요청 문제

문제

동시에 여러 요청을 보낸 후, 각 요청에서 401 에러가 발생하면 AT 재발급 요청이 여러 번 발생하였습니다.

원인

// authFallback.ts
export const authFallback = async <T>(
  error: Error,
  retryFn?: () => Promise<T>
): Promise<T | void> => {
  // error가 ApiError 인스턴스가 아니라면 예외 처리
  if (!(error instanceof ApiError)) {
    throw error;
  }

  // 401 에러: Access Token 만료
  if (error.status === 401) {
    try {
      // Access Token 재발급 요청
      const response = await fetch(
        `${import.meta.env.VITE_API_URL}${APIEndpoint.TOKEN_REISSUE}`,
        {
          credentials: "include",
          method: "POST",
        }
      );

      if (!response.ok) {
        // 재발급 실패 시 원래 에러를 throw
        throw error;
      }

      // 재발급 성공 시, 원래 요청 재시도
      if (retryFn) {
        return await retryFn();
      }

      return;
    }

    ...
  }

  ...
};

각 요청에서 401 에러가 발생하면, AT 재발급 요청을 보낸 후 다시 API 호출을 시도하는 로직이 있습니다.

하지만, 동시에 여러 요청이 발생하면, AT 재발급 요청이 여러 번 발생하였습니다.

해결책

동시 요청 문제를 해결하기 위해, 먼저 Axios Interceptor를 사용해 AT 재발급 요청을 처리하도록 하였습니다.

또한, 요청 큐를 사용해 이미 AT 재발급 요청이 진행 중이라면 다른 요청은 대기하도록 하였습니다.

// apiClient.ts
apiClient.interceptors.response.use(
  (response) => response,
  async (error) => {
    ...

    if (isExpiredAccessTokenError) {
      // 이미 AT 재발급 요청이 진행 중이라면 다른 요청은 대기하도록 하였습니다.
      if (isRefreshing) {
        return new Promise((resolve, reject) => {
          failedRequests.push({ resolve, reject });
        })
          .then(() => {
            return apiClient(originalRequest);
          })
          .catch((err) => {
            return Promise.reject(createApiError(err));
          });
      }

      isRefreshing = true;
      originalRequest._retry = true;

      try {
        await axios.post(API_URL + APIEndpoint.TOKEN_REISSUE, null, {
          withCredentials: true,
        });

        processQueue(null);

        return apiClient(originalRequest);
      } catch (refreshError) {
        ...
      }

      ...
    }
  }
);