일단 소셜로그인을 통해서 인가코드를 가져와서 백엔드에게 Access, refresh 토큰을 가져와서 교환하는 것까지는 진행을 했다. 밥먹고 오니까 토큰이 만료됬다고 나오더라 그래서 빨리 refresh 로직을 만들어야겠다고 생각을 했는데, 이때 Axios interceptor가 생각이 났다..

 


공식문서가서 다시 한번 읽어보자. 

 

https://axios-http.com/kr/docs/interceptors

 

인터셉터 | Axios Docs

인터셉터 then 또는 catch로 처리되기 전에 요청과 응답을 가로챌수 있습니다. axios.interceptors.request.use(function (config) { return config; }, function (error) { return Promise.reject(error); }); axios.interceptors.response.use(f

axios-http.com

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

axios interceptor는 서버로 내가 요청, 응답을 하게되는 프로세스가 발생하기전에 먼저 가로채서 동작을 수행하고 그에 따라서 옳게 진행된다면 그대로 그 프로세스가 진행될테고, 뭔가 잘못되거나 그랬을때는 어떤 조치를 먼저 취해주고 갈길을 보내주거나, 아니면 해당 프로세스를 멈출수가 있겠다라고? 나는 이해를 했다.

 


 

일단 access 토큰이 만료되거나 만료되서 쿠키에 아예 존재하지 않는 경우 두가지를 생각했다. 

일단 만료됬는지 확인 하는 방법은 해당 JWT를 decode해서 expiration시간과 현재시간을 비교하는 것이다.

대충 이런 느낌 ?

import dayjs from 'dayjs';
import jwt_decode from 'jwt-decode';

// 쿠키에서 access 토큰꺼내서 Decode한다음 user라는 변수에 할당 
const user: JWTType = jwt_decode(String(getCookie('access')));

// 현재 시간과 해당 토큰의 expiration날짜를 비교해서 만료되었는지 boolean 반환
const isExpired = dayjs.unix(user.exp).diff(dayjs()) < 1;

여기서 만료가 됬다면 isExpired가 True일것이고, 아니면 false겠죠 

 

그리고 애초에 access Token이 사라졌다면? 내가 알기론 expired 기간이 지나면 쿠키에서 사라진다고 알고 있다. 

그럴때는 refresh Token이 있는지 확인하고 refresh Token을 가져다가 Token을 다시 요청한다.

const refreshToken = getCookie('refresh');

// access 토큰 재요청 
const data = await refreshReq.post('/user/refresh/', {
      refresh: refreshToken,
	});

// 쿠키에 새 토큰들 다시 넣기
const { access, refresh } = data.data;
	setCookie('access', access);
	setCookie('refresh', refresh);

그리고 아무것도 없는 상태라면 ? 

return Promise.reject('login required');

Promise 를 종료시켜줍니다.

그러면 response 인터셉터에서 저 메세지를 받게되는데 해당 메세지를 받으면 redirect 시키도록 했습니다. 

인터셉터에서 바로 Router.push는 안되서 window.location을 사용해봤습니다.

 


 

동작 움짤

나의 코드

export const axiosInstance = axios.create({});

// axiosInstance 요청 인터셉터
axiosInstance.interceptors.request.use(
  async (req) => {
    const accessToken = getCookie('access');
    // access 토큰이 존재한다면 만료가 되었는지 확인 후 진행
    if (accessToken) {
      // jwt token 디코딩
      const user: JWTType = jwt_decode(String(getCookie('access')));
      // 현재 시간과 해당 토큰의 expiration날짜를 비교해서 만료되었는지 boolean 반환
      const isExpired = dayjs.unix(user.exp).diff(dayjs()) < 1;
      if (!isExpired) {
        console.log('req 정상');
        req.headers = {
          Authorization: `Bearer ${getCookie('access')}`,
        };
        return req;
      }
    }
    const refreshToken = getCookie('refresh');
    // access 토큰는 없는데 refresh 토큰이 있다면 토큰 재 요청
    if (refreshToken) {
      console.log('refresh 토큰');
      // 만료가 되었으니까 refresh해달라고 요청을 한다.
      const data = await refreshReq.post('/user/refresh/', {
        refresh: refreshToken,
      });
      // 새로 받은 토큰들 저장 후 기존 req 헤더에 넣어 재 요청
      const { access, refresh } = data.data;
      setCookie('access', access);
      setCookie('refresh', refresh);
      req.headers = {
        Authorization: `Bearer ${getCookie('access')}`,
      };
      return req;
    } else {
      return Promise.reject('login required');
    }
  },
  function (error) {
    return Promise.reject(error);
  },
);

// axiosInstance 응답 인터셉터
axiosInstance.interceptors.response.use(
  function (response) {
    console.log('res 정상');
    return response;
  },
  async function (error) {
    console.log('res 비정상');
    console.log(error);
    if (error === 'login required') {
      window.location.href = `/login`;
    }
    return Promise.reject(error);
  },
);

 

뭐가 이렇게 다 어려운지.. 정말 끝이 없는거 같음다.  분명히 더 좋은 방법이 있을거 같은데 저의 머리로는 역부족이네요 하핳