사용자 인증에는 크게 세션/쿠키 인증과 토큰 기반 인증이 있는데, 대표적인 토큰 기반의 인증으로 JWT(Json Web Token)을 많이 사용한다.
(인증 후 프론트엔드에서는 브라우저 저장소 중 쿠키에 토큰을 저장하는 경우가 많다)
JWT 토큰 인증 방식은 제 3자에게 토큰이 탈취되는 경우 보안에 취약하다는 단점이 있어 토큰에 유효기간을 부여하여 보안을 강화하게 되는데, 유효기간이 짧으면 사용자는 계속 로그인을 다시 해야하기 때문에 불편해 진다. 여기서 사용할 수 있는 것이 refresh token 을 활용한 토큰 리프레시다.
AccessToken 보다 유효기간이 긴 RefreshToken이라는 인증 장치를 하나 더 사용해 AccessToken을 재발급 해주는 것이다. RefreshToken 마저 만료되는 경우, 유저는 다시 로그인을 진행하게 된다.
요청에 대한 응답이 401 Error인 경우, refreshToken을 가지고 Token Refresh api 호출하는 방식으로 구현할 수 있으며, axios interceptors를 이용해 구현해 보겠다.
과정
요청성공시
요청실패시 ( 토큰 만료로 인한 에러 발생시)
코드 예시
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;