Axios interceptors로 토큰 연장하는 법!

Hyodduru ·2022년 8월 7일
17

React

목록 보기
18/22
post-thumbnail

Axios는 생각한 거 이상으로 다양한 기능이 있는 것 같다!
굉장히 편리함,,,

Axios interceptors를 활용하면 서버에 요청을 하기 전에 무언가를 처리할 수도 있고,
요청을 하고 나서 응답을 받았을 때도 미리 interceptors에서 무언가를 처리해줄 수 있다. (에러처리도 가능!)

그래서 오늘은 사이드 프로젝트를 현재 진행하면서, Axios interceptors를 활용하여 'Access Token'이 만료가 되었을 때, 자동으로 새로운 토큰을 발급받아서 재요청을 하는 법을 공유하고자 합니다 -!

🧐 프론트 쪽에서 토큰의 만료시간 확인하는 법

그 전에 먼저 알게 된 것을 공유하자면,

프론트 쪽에서도 토큰의 만료시간을 확인할 수 있다는 것이다!

크게 javascript 내장함수인 'atob'를 활용하거나, 혹은 'jwt-decode'라는 라이브러리를 활용하는 두 가지 방법이 있다.

참고한 자료 : https://www.bezkoder.com/handle-jwt-token-expiration-react/

나는 위의 자료를 참고하여 'atob'를 활용하여 토큰을 디코딩 해주었다.

const parseJwt = (token: string | null) => {
  if (token) return JSON.parse(atob(token.split('.')[1]));
};

export const AuthVerify = () => {
  const decodedAccess = parseJwt(access);
  const decodedRefresh = parseJwt(refresh);

  if (decodedAccess.exp * 1000 < Date.now()) {
    return 'Access Token Expired';
  }

  if (decodedRefresh.exp * 1000 < Date.now()) {
    return 'Refresh Token Expired';
  }

  return true;
};

위의 AuthVeirify 함수는 interceptors에서도 사용할 예정!

🧐 토큰 만료시 Interceptors로 토큰 자동 연장 및 재요청 해주기

1. custom axios를 생성해준다. (axios.create 활용)

import {access, refresh } from getToken;

const customHttp = axios.create({
  baseURL: BASE_URL,
  timeout: 8000,
  headers: {
    'Content-Type': `application/json`,
    access,
    refresh
  },
});

위의 custom axios 는 put, delete, post 요청을 할 때 사용하기 위한 axios이다. (get을 할때는 따로 headers에 토큰을 넣어줄 필요가 없어서 위의 axios를 활용하지 않음)

🔖 참고)
참고로, 위의 axios를 직접 사용하는 법은 다음과 같다.

const EditForm = async (values: FormatModel) => {
    try {
      const res = await customHttp.put(`/api/posts/${id}`, values);

      if (res.status === 200 && AuthVerify()) {
        message.success('해당 게시글이 수정되었습니다.');

        const id = (res.data as { id: number }).id;

        navigate(`/project/${id}`);
      }
    } catch (err) {
      console.log(err);
  };

위와 같이 custom axios + 원하는 http method 붙혀주면 된다!

2. interceptors를 활용하여 response를 가지고 무언가를 처리하기 전에, 이미 access token이 만료된 상태라면, DB에서 자동으로 발급받은 토큰을 새로 로컬 스토리지에 저장해준다.

3. 새로운 토큰을 다시 request 하는 config의 headers에 넣어준다.

4. 해당 axios를 활용하여 다시 재 요청한다.

5. 위의 조건에 해당하지 않는다면 그냥 response를 return한다.


export const renewAccessToken = (token: string) => {
  localStorage.removeItem('accessToken');
  setAccessToken(token);
};


export const onFulfilled = async (res: AxiosResponse) => {
  if (AuthVerify() === 'Access Token Expired') {
    const { access: newAccess } = res.data;

    renewAccessToken(newAccess);

    res.config.headers = {
      'Content-Type': `application/json`,
      refresh: refresh ?? '',
      access: newAccess,
    };

    return await axios(res.config);
  } else return res;
};

customHttp.interceptors.response.use(onFulfilled);

✔️ 참고로, refresh token은 유효하고, 이미 access token은 만료된 상태라면, 서버에서 자동으로 새로운 토큰을 발급받아준다. (우리가 진행한 사이드 프로젝트에서는 그렇게 진행하였음! )

그래서 response를 받으면, 그 안에 담겨있는 새로운 토큰을 localstorage에 다시 저장해주고, response의 config내의 headers안에 새로운 토큰을 다시 갈아끼워주고,

새로운 axios(res.config)를 return 함으로써 다시 해당 요청을 하는 것이다!

👉 이렇게하면, 위의 과정이 필요한 모든 함수들의 response에 일일히 위의 코드들을 적어줄 필요도 없고, 한번에 처리가 가능하다! 또한 함수를 함수 내에서 따로 재요청을 해주지 않아도, interceptors에서 알아서 걸러서 처리를 해주니 매우 편리함!

interceptors에서 에러처리 또한 가능하다.

👉 interceptors의 두번째 인자에 넣어주면 된다.

ex)


const onRejected = (err) => {return Promise.reject(err)}

customHttp.interceptors.response.use(onFulfilled, onRejected);

요청하기 전에 로직 처리하는 법 👉 interceptors.request

response 처리하는 법과 동일! 다만 요청하기 전의 처리과정이므로, response가 아닌 config를 받아온다는 차이가 있음!

이 때 headers에 토큰을 넣어줄 수도 있음!

customHttp.interceptors.request.use(
  async (config: AxiosRequestConfig) => {
  
    if (token) {
      config.headers['Content-Type'] = 'application/json';
      config.headers.Authorization = token;
    }
    return config;
  },
  err => {
    return Promise.reject(err);
  },
);

🍯 마무리

처음에는 모르고 저기 위의 onFulfilled라는 함수 내에 useNavigate hook 넣었다가, 오류 먹고 아 왜안돼,,, 왜이러나 싶었는데 생각해보니, hook 내의 hook을 넣어주면 안된다는 오류가 있었다...⭐️ 그래서 처음에 엄청 헤맸었는데 알고보니 엄청 간편하고 편리했던 아이,, 첨에 욕해서 미안하다(?)

profile
꾸준히 성장하기🦋 https://hyodduru.tistory.com/ 로 블로그 옮겼습니다

0개의 댓글