오늘 배운 것

저번에 멘토님께 피드백을 받은 대로 Context API에 액세스토큰 값을 넣어두는 대신 Api 클래스를 싱글톤 패턴으로 만들고, 해당 클래스의 메소드를 사용하도록 컴포넌트 내부의 코드들도 수정해주었다. 그런데도 여전히 401 AxiosError가 발생하고 있었다. 

 

해당 에러를 수정해야 무사히 PR을 하고 잘 동작하는 앱 화면을 볼 수 있기에, 여기에 달려있는 dependency가 많다고 느껴져서 이 일을 급하다고 판단했다. 

 

현재 Api 클래스의 일부 코드는 다음과 같다. 여기서 핵심은 request 메소드에서 this.accessToken으로 Api 싱글톤 클래스가 갖고 있는 토큰값을 넣어주고, 에러가 날 경우 Sentry에 로그를 남긴 뒤 해당 에러가 액세스토큰이 만료되어 발생하는 401 AxiosError라면 액세스토큰을 갱신시키고 다시 시도하는 로직이다. 

class Api {

  // 싱글톤 패턴으로 구현

  async request(url, options) {
    try {
      const response = await axios.request(url, {
        ...options,
        'Content-Type': 'application/json',
        Authorization: `Bearer ${this.accessToken}`,
      });
      return response.data;
    } catch (e) {
      Sentry.captureException(e);
      if (axios.isAxiosError(e)) {
        if (
          (e.response.status === 401 &&
            e.response.data.detail === TOKEN_INVALID_OR_EXPIRED_MESSAGE) ||
          e.response.data.detail === TOKEN_INVALID_TYPE_MESSAGE
        ) {
          // access token 재발급
          const responseData = await axios.post(API_PATH.renew, {
            refresh: this.refreshToken,
            access: this.accessToken,
          });
          const newAccessToken = responseData.data.access;
          await AsyncStorage.setItem('accessToken', newAccessToken);
          this.accessToken = newAccessToken;

          // 다시 요청
          return axios.request(url, {
            ...options,
            'Content-Type': 'application/json',
            Authorization: `Bearer ${this.accessToken}`,
          });
        }
      }
    }
  }

  fetchTodos(userId) {
    return this.request(`${API_PATH.todos}?user_id=${userId}`, {
      method: 'GET',
    });
  }

  // 다른 투두 메소드들
  
}

export default Api;

 

그런데 로그를 찍어보니 401 AxiosError가 발생하였다고 콘솔에는 나오는데 catch 문에서 해당 에러를 못 잡고 있었다. 대신 콘솔에서는 react query의 useQuery 메소드에서 에러가 호출되고 있었다. 내가 원하는 건 useQuery에러 에러를 캐치하기 전에 위의 request 메소드에서 에러를 먼저 캐치하고, 액세스토큰 관련 에러일 경우 에러가 나지 않도록 액세스토큰을 갱신하는 것이었다. 

 

 

질문을 통해 얻은 해결 방법은 axios의 interceptor 기능을 사용하는 것이었다. 제시된 다른 해결방법 중에는 react query에서 axios 인스턴스를 직접 사용하거나 react query의 메소드(useQuery, useMutation)에서 onError 파라미터로 401 에러가 났을 때 그것을 처리하는 함수를 인자로 넘겨주는 방법도 있었다. 그러나 지금 하려는 작업은 API 서버로 보내는 모든 요청에 대해서 적용되어야 했기 때문에 이 방법들을 쓰면 각 API를 요청하는 코드를 일일이 수정해 주어야 해서 번거로웠다. 정말 신기했던 게 어제 멘토링을 하면서 멘토님도 axios의 interceptor 기능을 추천해주셨었다. 이 상황을 미리 내다보신 것일까? 신기했다. 

 

그런데 별도의 파일에 axios interceptor를 적용하고 그 axios 인스턴스를 import해서 사용하는 기존 방법은 기존에 만들었던 Api 싱글톤 인스턴스와는 별개로 동작하는 방법이었다. 나는 Api 싱글톤 인스턴스를 사용하면서, 그 안의 request 메소드에서 사용하는 axios 인스턴스에 interceptor를 적용하고 싶었다. 

 

 

적용하려는 두 가지 방법(Api 싱글톤 클래스, axios 인스턴스)은 별도의 문제였다. 즉 둘 다 같이 적용할 수 있었다. Api 싱글톤 클래스 안에 axios 인스턴스를 만들고, 그 인스턴스에 대해 interceptor 기능을 적용하면 되는 문제였다. 

 

그랬더니 이번에는 401 대신 400 AxiosError가 떴다. 이것은 클라이언트에서 요청을 뭔가 잘못 보내고 있는 문제이니, 로직을 수정해보면 될 것 같다. 

 

일단 콘솔로 로그를 찍어서(바람직하지 못한 방법이긴 하다... 중간평가 끝나고는 꼭 디버거를 써 봐야지) 에러 객체를 그대로 찍어봤다. 

 

인내심을 가지고 로그를 쭉 읽다 보니 응답 정보 쪽에 이런 메시지가 보였다. 

"_response": "{\"refresh\":[\"This field is required.\"]}"

 

해당 코드는 401 에러가 나서 토큰 갱신 API를 다시 호출하고 있는 로직에서 난 400 에러를 캡처한 부분이다. 즉 토큰 갱신 API를 통해 액세스토큰을 갱신하려면 파라미터로 리프레시 토큰을 넣어줘야 하는데, 그 부분이 빠져서 400에러가 난 것으로 보였다. 

 

+ Recent posts