axios interceptors를 이용해 token만료시 refreshToken 자동요청

togongs·2022년 9월 8일
10

2022

목록 보기
17/19

사용자 인증에는 크게 세션/쿠키 인증과 토큰 기반 인증이 있는데, 대표적인 토큰 기반의 인증으로 JWT(Json Web Token)을 많이 사용한다.
(인증 후 프론트엔드에서는 브라우저 저장소 중 쿠키에 토큰을 저장하는 경우가 많다)

JWT 토큰 인증 방식은 제 3자에게 토큰이 탈취되는 경우 보안에 취약하다는 단점이 있어 토큰에 유효기간을 부여하여 보안을 강화하게 되는데, 유효기간이 짧으면 사용자는 계속 로그인을 다시 해야하기 때문에 불편해 진다. 여기서 사용할 수 있는 것이 refresh token 을 활용한 토큰 리프레시다.

AccessToken 보다 유효기간이 긴 RefreshToken이라는 인증 장치를 하나 더 사용해 AccessToken을 재발급 해주는 것이다. RefreshToken 마저 만료되는 경우, 유저는 다시 로그인을 진행하게 된다.

요청에 대한 응답이 401 Error인 경우, refreshToken을 가지고 Token Refresh api 호출하는 방식으로 구현할 수 있으며, axios interceptors를 이용해 구현해 보겠다.

과정

  • 요청성공시

    • return
  • 요청실패시 ( 토큰 만료로 인한 에러 발생시)

    • 에러를 반환하기 전에 interceptor 해서, 서버에 token refresh를 요청합니다.
    • token refresh요청을 통해 받은 새로운 accessToken을 ReduxStore등 본인이 이용하는 저장소에 저장합니다.
    • 새로운 accessToken을 header에 담아, 재요청합니다.
  • 코드 예시

import axios from "axios";

// url 호출 시 기본 값 셋팅
const api = axios.create({
  baseURL: "https://api.themoviedb.org/3",
  headers: { "Content-type": "application/json" }, // data type
});

// Add a request interceptor
api.interceptors.request.use(
  function (config) {
    const token = localStorage.getItem("token");

    //요청시 AccessToken 계속 보내주기
    if (!token) {
      config.headers.accessToken = null;
      config.headers.refreshToken = null;
      return config;
    }

    if (config.headers && token) {
      const { accessToken, refreshToken } = JSON.parse(token);
      config.headers.authorization = `Bearer ${accessToken}`;
      config.headers.refreshToken = `Bearer ${refreshToken}`;
      return config;
    }
    // Do something before request is sent
    console.log("request start", config);
  },
  function (error) {
    // Do something with request error
    console.log("request error", error);
    return Promise.reject(error);
  }
);

// Add a response interceptor
api.interceptors.response.use(
  function (response) {
    // Any status code that lie within the range of 2xx cause this function to trigger
    // Do something with response data
    console.log("get response", response);
    return response;
  },
  async (error) => {
    const {
      config,
      response: { status },
    } = error;
    if (status === 401) {
      if (error.response.data.message === "expired") {
        const originalRequest = config;
        const refreshToken = await localStorage.getItem("refreshToken");
        // token refresh 요청
        const { data } = await axios.post(
          `http://localhost:3000/refreshToken`, // token refresh api
          {},
          { headers: { authorization: `Bearer ${refreshToken}` } }
        );
        // 새로운 토큰 저장
        // dispatch(userSlice.actions.setAccessToken(data.data.accessToken)); store에 저장
        const { accessToken: newAccessToken, refreshToken: newRefreshToken } =
          data;
        await localStorage.multiSet([
          ["accessToken", newAccessToken],
          ["refreshToken", newRefreshToken],
        ]);
        originalRequest.headers.authorization = `Bearer ${newAccessToken}`;
        // 401로 요청 실패했던 요청 새로운 accessToken으로 재요청
        return axios(originalRequest);
      }
    }
    // Any status codes that falls outside the range of 2xx cause this function to trigger
    // Do something with response error
    console.log("response error", error);
    return Promise.reject(error);
  }
);

export default api;

참고 : 링크텍스트
링크텍스트

profile
개발기록

0개의 댓글