🗃️ 내가 다시 볼 것

[React] Refresh Token을 활용해 토큰 재발급 받기!

전호영 2024. 1. 26. 01:36

아래 글로 리뉴얼 되었습니다.
https://securityinit.tistory.com/248

 

토큰을 재발급 받는 로직은 다음과 같다.

매우매우 간소화한 로직

백엔드에선 단순히 토큰을 던져주고, 토큰 재발급을 위한 엔드포인트를 만들어주기만 하면 됐다.

프론트에선 꽤 많은 어려움이 있었다.

리액트에서 axios를 활용해 토큰을 재발급 받는 로직을 작성했다.

 

axios엔 interceptors가 있다.

axios의 intercpetors는 then 또는 catch로 우리 응답이 처리되기 전에 요청과 응답을 가로챌 수 있도록 해준다.

현재 우리는 accessToken 관련 에러가 발생했을 때 catch로 처리되기 전, 가로채 accessToken을 재발급 받은 뒤 다시 요청을 보내려 한다.

 

axios.interceptors.response.use(
  (response) => { // 정상 응답
    return response;
  },

  async (error) => {
  	// 에러가 발생했을 경우.
    // catch로 처리되기 전 가로챔!
    const originalConfig = error.config; // 서버에게 보내려고 했던 요청
    const { logout } = useAuthStore.getState(); 
    if (error.response.status === 401) { // 토큰이 만료된 경우
      try {
        console.log("roatate token");
        const refreshToken = localStorage.getItem("refreshToken");
        if (!refreshToken) {
          throw new Error("Refresh token not available.");
        }
        const res = await axios.create().post(
          "/api/auth/refresh",
          {},
          {
            baseURL: "http://localhost:3001",
            headers: {
              Authorization: `Bearer ${refreshToken}`,
              "Content-type": "application/json",
            },
          }
        );

        const newAccessToken = res.data.accessToken.newAccessToken; // 새로운 토큰을 발급 받음.
        if (newAccessToken) {
          localStorage.setItem("accessToken", newAccessToken);
          originalConfig.headers.Authorization = `Bearer ${newAccessToken}`; // 새로운 엑세스 토큰을 저장
          return axios(originalConfig); // 다시 재요청
        }
      } catch (refreshError) {
      	// 리프레시 토큰이 없는 경우, 또는 만료된 경우 로그아웃
        console.error(refreshError); 
        localStorage.clear();
        logout();
        return Promise.reject(refreshError);
      }
    }

    localStorage.clear();
    return Promise.reject(error);
  }
);

 

위 로직을 통해 토큰을 재발급 받았다.

 

axios.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem("accessToken");
    config.headers.Authorization = `Bearer ${token}`;

    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

 

 

axios.interceptors.request.use

위 로직은 요청의 응답이 가기전 가로채는 인터셉터이다.

미리 가로채 우리가 새로 발급받은 accessToken으로 헤더를 설정해주는 코드이다. 

 

 

전체 코드

import axios from "axios";
import useAuthStore from "store/useAuthStore";

axios.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem("accessToken");
    config.headers.Authorization = `Bearer ${token}`;

    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

axios.interceptors.response.use(
  (response) => {
    return response;
  },

  async (error) => {
    const originalConfig = error.config;
    const { logout } = useAuthStore.getState();
    if (error.response.status === 401) {
      try {
        console.log("roatate token");
        const refreshToken = localStorage.getItem("refreshToken");
        if (!refreshToken) {
          throw new Error("Refresh token not available.");
        }
        const res = await axios.create().post(
          "/api/auth/refresh",
          {},
          {
            baseURL: "http://localhost:3001",
            headers: {
              Authorization: `Bearer ${refreshToken}`,
              "Content-type": "application/json",
            },
          }
        );

        const newAccessToken = res.data.accessToken.newAccessToken;
        if (newAccessToken) {
          localStorage.setItem("accessToken", newAccessToken);
          originalConfig.headers.Authorization = `Bearer ${newAccessToken}`;
          return axios(originalConfig);
        }
      } catch (refreshError) {
        console.error(refreshError);
        localStorage.clear();
        logout();
        return Promise.reject(refreshError);
      }
    }

    localStorage.clear();
    return Promise.reject(error);
  }
);