Axios token verification

강정우·2023년 10월 17일
0

프로젝트

목록 보기
11/11
post-thumbnail
post-custom-banner

Axios의 개꿀기능

  • 사실 Promise, fetch를 대신하여 사용하기 매우 편리한 Axios를 예전부터 사용해왔다. 하지만 오늘 매우 개꿀 기능을 알아와서 공유하고자 포스팅한다.

  • 대충 개념은 아래 사진과 같다.

intercepors

  • 바로 오늘의 주인공 인터셉터이다. 이 속성을 이름값을 하는데 axios로 생성된 모든 req를 날리기 전 use() 메서드로 등록되어있는 함수를 실행한다. 그럼 이를 이용하여 토큰을 아주 편하게 조리할 수 있다.

1. Axios instance 생성

  • 우선 그럼 앞서 언급한 axios로 생성된 모든 req를 간편하게 만들어주는 Axios instance부터 작성해보자.
import axios from 'axios';

const instance = axios.create({
  baseURL: 'YOUR_API_BASE_URL',
});
  • 참고로 여기에 baseURL을 하나 더 파서 프로덕션용, 개발용 구별하면 매우 편리하다.

2. Axios intercepors 작성

  • 보통 이를 미들웨어라고 부른다.
instance.interceptors.request.use(
  async (config) => {
    const token = localStorage.getItem('token');

    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }

    return config;
  },
  async (error) => {
    if (error.response && error.response.status === 401) {
      const refreshToken = localStorage.getItem('refreshToken');
      if (refreshToken) {
        try {
          const response = await axios.post('/api/refresh-token', { refreshToken });
          const newAccessToken = response.data.accessToken;
          localStorage.setItem('token', newAccessToken);
          // 원래 req를 요청
          const originalRequest = error.config;
          originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
          return axios(originalRequest);
        } catch (refreshError) {
          // 리프레시 토큰을 사용한 토큰 갱신에 실패한 경우 로그아웃 또는 다른 조치를 취할 수 있다.
          // 여기서는 그냥 localstorage를 밀어버렸다.
          localStorage.clear();
        }
      }
    }
    return Promise.reject(error);
  }
);

export default instance;

즉, Axios 인스턴를 사용하여 req를 날리기 이전에 토큰 검사 미들웨어를 추가해야 한다.

  • 이렇게 구현하면 Axios를 사용하여 REST API를 호출하면 모든 요청 이전에 토큰의 유효성이 검사된다.
    토큰이 없거나 만료된 경우, 서버에서 적절한 응답을 보내므로 클라이언트에서 해당 응답을 처리할 수 있다.

  • 아래는 내 상황에 맞는 예제코드이다.

예제코드

instance_Api_A.interceptors.request.use(
    async (config) => {
        const token = getCookie('token')
        if (token) {
            config.headers.Authorization = `Bearer ${token}`;
            config.headers["Content-Type"] = 'application/json';
        }
        return config;
    },
    async (error) => {
      	// 우리는 403 forbidden으로 넘어온다.
        if (error.response && error.response.status === 403) {
            const refreshToken = getCookie('refreshToken')
            if (refreshToken) {
                // 리프레시 토큰을 사용하여 새로운 액세스 토큰을 요청
                try {
                    const userId = localStorage.getItem('userId');
                    const payload = {userId, refreshToken}
                    const {data} = await instance_Api_A.post('/common/token-refresh', {payload });
                    const newAccessTokenData = data.tokenInfoDTO;
                    const formattedDate1 = formatDate(newAccessTokenData.tokenExpDt)
                    const formattedDate2 = formatDate(newAccessTokenData.refreshExpDt)
                    document.cookie = `token=${newAccessTokenData.token}; expires=${formattedDate1}; path=/;`
                    document.cookie = `refreshToken=${newAccessTokenData.refresh}; expires=${formattedDate2}; path=/;`
                    const originalRequest = error.config;
                    originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
                    return axios(originalRequest);
                } catch (refreshError) {
                    localStorage.clear();
                    $router.push('/login');  // 여기는 vue.js 문법이다. 
                }
            }
        }
        return Promise.reject(error);
    }
);

profile
智(지)! 德(덕)! 體(체)!
post-custom-banner

0개의 댓글