axios interceptor로 401 처리하기

고광필·2022년 8월 18일
4

프로젝트에 Axios를 도입하면서 interceptor를 사용해서
401에 대한 에러 처리를 해본 경험, token을 가져오는 로직을 리팩토링한 경험을 공유합니다

Axios란?

클라이언트에서 요청을 보낼 때 fetch()를 사용할 수 있습니다
Axios는 fetch()보다 더 다양한 기능을 제공하는 라이브러리입니다

대표적인 특징은 아래와 같습니다

  • node에서도 사용할 수 있다
  • 요청을 취소할 수 있다
  • 요청과 응답을 가로챌 수 있다
  • Promise 기반이고, response.data로 데이터에 접근할 수 있다

Axios interceptor란?

Axios docs - interceptor를 보면 Promise 응답에 대해 then, catch로 처리되기 전에 요청과 응답을 가로챌 수 있다고 나와있습니다

interceptor를 사용하면 요청이나 응답 전에 어떤 로직을 실행시킬 수 있습니다

사용 사례 코드

기존 코드

axios.interceptors.request.use(
  (config) => {
    return config;
  },
  (error) => {
    console.error(error);
    return Promise.reject(error);
  }
);

axios.interceptors.response.use(
  (res) => {
    if (!(res.status === 200 || res.status === 201 || res.status === 204))
      throw new Error();

    if (res.data.errors) throw new Error(res.data.errors);

    return res.data.data;
  },
  (error) => {
    console.log(error.response.data.errors);

    return Promise.reject(error);
  }
);

/* .. */

export const getMyInfo = async (token: string) => {
  const data = await axios.get<DataType, DataType>(
    `${END_POINT.getMyInfo}`,
    {
      headers: {
        Authorization: `Bearer ${token}`,
      },
    }
  );

  return data;
};

기존의 axios 코드는 프로젝트 팀원분께서 작성해주신 코드를 사용했습니다
interceptor는 에러 로깅 목적이 강했습니다

또한 토큰을 필요로 하는 요청에 대해서 그 요청을 사용할 때 토큰을 인자로 넘겨주었습니다

로그인 이후에 보내는 요청들은 대부분이 토큰을 필요로 하는데 좀더 쉬운방법은 없을까 싶어서 Axios 코드를 수정했습니다

수정 코드

interface HeaderType extends AxiosRequestHeaders {
  ["Content-Type"]: string;
  Authorization: string;
}

// interceptor
axios.interceptors.request.use(
  (config) => {
    const headers = config.headers as HeaderType;
    const token = document.cookie 에서 가져오기

    if (token) {
      headers["Content-Type"] = "application/json";
      headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

request interceptor는 모든 요청들을 보내기 전에 실행합니다
따라서 이 부분에서 cookie에 token이 있는지 보고, 있으면 요청에 헤더에 넣어줍니다

타입스크립트라서 관련 타입도 정의해줍니다

axios.interceptors.response.use(
  (res) => {
    if (!(res.status === 200 || res.status === 201 || res.status === 204))
      throw new Error();

    if (res.data.errors) throw new Error(res.data.errors);

    return res.data.data;
  },
  async (error) => {
    const err = error as AxiosError;

    if (err.response?.status === 401) {
      const data = err.response.data as ErrorDataType;

      if (액세스 토큰 만료) {
        const token = await axios.get('토큰 갱신');
        document.cookie = `token=${token}; path=/; max-age=토큰수명`;
        err.config.headers = {
          "Content-Type": "application/json",
          Authorization: `Bearer ${accessToken}`,
        };
        // 중단된 요청 (에러난 요청)을 새로운 토큰으로 재전송
        const originalResponse = await axios.request(err.config);
        return originalResponse.data.data;
      }
    }

    return Promise.reject(error);
  }
);

response interceptor에서 401일 때 특정 로직을 수행하게 합니다

액세스 토큰이 만료되었는지 확인하고, 토큰을 갱신하는 요청을 보냅니다
갱신하는 요청 역시 request interceptor로 인해 기존의 토큰을 헤더에 추가하게 됩니다

응답으로 갱신된 토큰이 오게 되고, 새로운 토큰을 쿠키에 저장하고, 헤더에 넣어줍니다

그리고 401 에러로 중단된 요청을 새 토큰을 포함해서 다시 전송합니다

이렇게 되면 401에러가 발생했을 시 사용자는 401이 발생한것을 눈치채지 못합니다
자동으로 로그인이 갱신된것입니다

정리

Axios를 interceptor로 다뤄본것은 처음이었는데
요청과 응답을 가로채서 실행할 수 있는것이 큰 메리트로 느껴졌습니다

참고

Axios docs

profile
이해하는 개발자를 희망하는 고광필입니다.

0개의 댓글