아래 글로 리뉴얼 되었습니다.
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);
}
);