[React] Axios 커스터마이징

Lian Kim·2022년 11월 27일
0

Axios는 node.js와 브라우저를 위한 Promise 기반의 HTTP 비동기 통신 라이브러리로, JSON 데이터 자동 변환, 요청 및 응답 인터셉터 기능 등 다양한 장점을 가지고 있다.

Axios를 사용할 때마다 헤더에 토큰을 넣어주거나, baseURL을 작성하는 등 반복되는 코드를 작성하지 않기 위해서 커스터마이징한 인스턴스를 모듈화하여 사용했다. 새로운 인스턴스를 생성한 후, interceptor에서 토큰 관련 부분을 알아서 처리하도록 만들어보았다.

커스터마이징 과정은 다음과 같다.

  1. create - 새로운 인스턴스 생성
  2. interceptor - 요청 및 응답 인터셉트




인스턴스 생성

axios.create([config])

사용자 지정 config로 Axios instance를 만들 수 있다.
config 안에는 baseURL, timeout, withCredentials 등 다양한 옵션을 지정할 수 있다.


이번 프로젝트에서는 아래와 같이 baseURL만 지정해줬다.

const customAxios = axios.create({
  baseURL: SERVER_BASE_URL,
});




interceptor

interceptor를 사용하면 then 또는 catch로 처리되기 전에 요청과 응답을 가로챌 수 있다.

const instance = axios.create();
instance.interceptors.request.use(function () {/*...*/});


요청 interceptor

axios.interceptors.request.use(
  function (config) {
    // 요청이 전달되기 전에 작업 수행
    return config;
  },
  function (error) {
    // 요청 오류가 있는 작업 수행
    return Promise.reject(error);
  });

✔️ authorization header

게시글 조회 페이지와 같이 비로그인 유저도 접근할 수 있는 페이지들이 있지만, 북마크 여부 등 토큰 유무에 따라 받아오는 데이터 값이 다르기 때문에 토큰을 확인하는 절차가 필수였다. 요청을 인터셉트해서 access token의 정보를 넣어주도록 하였다.

  1. access token 값의 유무 확인
  2. 토큰이 있으면 헤더의 Authorization에 Bearer ${token} 형태로 토큰 값을,
    토큰이 없으면 빈 문자열을 넣어준다.
customAxios.interceptors.request.use(
  (config) => {
    const nextConfig = config;
    const accessToken = getAccessToken();
    nextConfig.headers.Authorization = accessToken
      ? `Bearer ${accessToken}`
      : '';
    
    return nextConfig;
  },
  (error) => {
    console.log(error);
    return Promise.reject(error);
  },
);


응답 interceptor

axios.interceptors.response.use(
  function (response) {
    // 2xx 범위에 있는 상태 코드는 이 함수를 트리거 한다.
    // 응답 데이터가 있는 작업 수행
    return response;
  },
  function (error) {
    // 2xx 외의 범위에 있는 상태 코드는 이 함수를 트리거 한다.
    // 응답 오류가 있는 작업 수행
    return Promise.reject(error);
  });

🔄 token refresh 요청

access token의 유효 시간은 30분, refresh token의 유효 시간은 24시간으로 설정되어 있다.
응답을 가로채서 토큰 만료 에러가 뜨면 서버에 토큰 재발행 요청을 보내도록 했다.

  1. 에러코드가 'EXPIRED_ACCESS_TOKEN'인지 확인
  2. 기존의 access token과 refresh token의 값을 서버에 전달하여 토큰 재발행 요청
  3. 재발행된 토큰 값을 저장소에 저장
  4. 재발행된 토큰 값을 헤더에 넣어 재요청
customAxios.interceptors.response.use(
  (response) => response,
  async (error) => {
    // response에서 error가 발생했을 경우 catch로 넘어가기 전에 처리하는 부분
    const { errorCode } = error?.response?.data;
    const originalRequest = error?.config;
    if (
      errorCode === 'EXPIRED_ACCESS_TOKEN' &&
      !originalRequest.retry
    ) {
        const refreshToken = await getRefreshToken();
        const accessToken = await getAccessToken();
        const res = await customAxios.post(
          '/member/reissue',
          { accessToken, refreshToken }
        );
        const { newAccessToken, newRefreshToken } = res.data;
        setAccessToken(newAccessToken);
        setRefreshToken(newRefreshToken);
        originalRequest.headers.Authorization = `Bearer ${accessToken}`;

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




전체 코드

// src/apis/core/instance.js

import axios from 'axios';
import { SERVER_BASE_URL } from '../../constants/serverBaseUrl';
import {
  setAccessToken,
  getAccessToken,
  removeAccessToken,
} from '../../utils/controlAccessToken';
import {
  setRefreshToken,
  getRefreshToken,
  removeRefreshToken,
} from '../../utils/controlRefreshToken';

const customAxios = axios.create({
  baseURL: SERVER_BASE_URL,
});

customAxios.interceptors.request.use(
  (config) => {
    const nextConfig = config;
    const accessToken = getAccessToken();
    nextConfig.headers.Authorization = accessToken
      ? `Bearer ${accessToken}`
      : '';
    
    return nextConfig;
  },
  (error) => {
    console.log(error);
    return Promise.reject(error);
  },
);

customAxios.interceptors.response.use(
  (response) => response,
  async (error) => {
    // response에서 error가 발생했을 경우 catch로 넘어가기 전에 처리하는 부분
    const { errorCode } = error?.response?.data;
    const originalRequest = error?.config;
    if (
      errorCode === 'EXPIRED_ACCESS_TOKEN' &&
      !originalRequest.retry
    ) {
        const refreshToken = await getRefreshToken();
        const accessToken = await getAccessToken();
        const res = await customAxios.post(
          '/member/reissue',
          { accessToken, refreshToken }
        );
        const { newAccessToken, newRefreshToken } = res.data;
        setAccessToken(newAccessToken);
        setRefreshToken(newRefreshToken);
        originalRequest.headers.Authorization = `Bearer ${accessToken}`;

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

export default customAxios;




참고 자료
Axios 공식문서

0개의 댓글