=> 토큰 인증을 구현할 때 유효기간을 짧게 설정한 accessToken으로 인증하고, 이 accessToken으로이 만료되었을 때,유효기간을 길게 설정한 refreshToken을 통해 accessToken을 재발급 받는다.
토큰 인증을 받기 위해서 클라이언트에 저장된 토큰을 서버로 보내야하며
이 과정이 로그인 시 이루어져야 한다.
=> 로그인 할 때 이루어지려면 비동기 통신 시 토큰 정보를 보내야하는데, 이때 axios interceptor를 사용
axios interceptor란, 비동기 통신이 이루어지기 정에 요청이나 응답을 가로채 작업을 추가로 수행한 뒤 통신이 이루어지게 하는 기능
별도의 Library가 아닌 axios에 포함된 기능이다. 요청이나 응답 전에 무엇인가를 수행해주거나, 오류 발생시에 수행할 것들을 미리 정의해둘 수 있다.
토큰 인증시 요청을 보낼 때 헤더에 accessToken을 함께 실어보내고, 응답을 받을 때 에러가 발생(accessToken 만료)하면 refreshToken을 확인하여 재발급을 요청한다.
요청성공시
return
요청실패시 ( 토큰 만료로 인한 에러 발생시)
코드 예시
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;
=> 요약 : 엑세스토큰보다 유효기간이 긴 리프레쉬 토큰을 추가하여 엑세스토큰을 재발급한다. 리프레쉬 토큰이 만료된느 경우에는 다시 로그인을 해야한다.
토이 프로젝트를 진행하면서 서버에 토큰 인증을 필요로 하는 API 요청을 할때마다 HTTP Authorization 요청 헤더에 토큰을 넣어줘야하고 401(Unauthorized) 에러가 서버로부터 들어오면 토큰을 갱신해준 후 재요청을 보내는 과정을 한 곳에서 모두 처리하여 중복 코드를 제거하고 유지보수성을 향상시키기 위해 Axios 인터셉터를 적용하기로 했다.
사용자 지정 config로 새로운 Axios 인스턴스를 생성하여 사용하니 더욱 편리했다. (baseURL, timeout 설정)
const instance = axios.create({
// 상대적인 URL을 인스턴스 메서드에 전달하려면 baseURL을 설정하는 것은 편리하다.
// URL(서버 주소) 예시 - http://127.0.0.1:5500
baseURL: URL,
// 요청이 timeout보다 오래 걸리면 요청이 중단된다.
timeout: 1000,
});
instance.interceptors.request.use(
(config) => {
// getToken() - 클라이언트에 저장되어 있는 액세스 토큰을 가져오는 함수
const accessToken = getToken();
config.headers['Content-Type'] = 'application/json';
config.headers['Authorization'] = `Bearer ${accessToken}`;
return config;
},
(error) => {
console.log(error);
return Promise.reject(error);
}
);
instance.interceptors.response.use(
(response) => {
if (response.status === 404) {
console.log('404 페이지로 넘어가야 함!');
}
return response;
},
async (error) => {
if (error.response?.status === 401) {
// isTokenExpired() - 토큰 만료 여부를 확인하는 함수
// tokenRefresh() - 토큰을 갱신해주는 함수
if (isTokenExpired()) await tokenRefresh();
const accessToken = getToken();
error.config.headers = {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
};
// 중단된 요청을(에러난 요청)을 토큰 갱신 후 재요청
const response = await axios.request(error.config);
return response;
}
return Promise.reject(error);
}
);
https://gusrb3164.github.io/web/2022/08/07/refresh-with-axios-for-client/
참고 출처
https://velog.io/@bnb8419/Axios-Interceptor를-통해-Refresh-Token으로-Access-Token-재발급하기