유저 정보를 accessToken을 통해 받아오고, accessToken이 만료되었다면
refreshToken을 통해서 새 accessToken을 받아오는 로직
axiosInstance.interceptors.response.use((res) => {
return res;
},
async(err) => {
const { config, response:{status}} = err;
if(status === 403 || status === 500) {
const originalRequest = config;
if(!isTokenRefreshing){
isTokenRefreshing = true;
const token = getStoredToken()
const {data} = await axiosInstance.put('/member/newAccess',{},
{
headers:getNewJWTToken(token)
});
console.log(data);
isTokenRefreshing = false;
setStoredToken(data);
axiosInstance.defaults.headers.common["X-ACCESS-TOKEN"] = data.accessToken;
const retryOriginalRequest = new Promise((resolve) => {
addRefreshSubscriber((accessToken:string,refreshToken:string) => {
originalRequest.headers["X-ACCESS-TOKEN"] = accessToken
originalRequest.headers["X-REFRESH--TOKEN"] = refreshToken
resolve(axios(originalRequest))
})
})
onTokenRefreshed(data.accessToken)
return retryOriginalRequest;
}
return Promise.reject(err);
}
})
하나씩 뜯어보자면
axiosInstance.interceptors.response.use((res) => {
return res;
}
interceptors는 response,request에 사용할 수 있다.
response는 응답을 받기 전에 처리하는 로직이고 request는 요청을 하기 이전에 처리한다.
여기서는 response로 처리.
interceptors.response.use는 2개의 callback을 받는다.
첫번째 callback은 정상적으로 response를 받았을 때(resolve) 어떻게 처리할 것인지, 두번째는 Error를 받았을 때 어떻게 처리할 것인지 로직을 작성한다.
정상적으로 응답을 받았을 때에는 응답 그 자체를 그대로 return한다.
async(err) => {
const { config, response:{status}} = err;
if(status === 403) {
const originalRequest = config;
if(!isTokenRefreshing){
isTokenRefreshing = true;
const token = getStoredToken()
Error는 토큰 에러 뿐만 아니라 다양한 에러가 발생할 수 있기에 사전에
accessToken 에러라면 어떤 status를 받을 것인지 백앤드 개발자와 사전약속이 되어있어야 한다.
여기서는 403 status를 받기로 약속했다.
403에러를 받았다면, 새 accessToken을 넣어 준 뒤에 다시 요청을 보내줘야 하기에 config를 받아와야 한다.
여기서는 originalRequest라는 변수로 config를 할당했다.
isTokenRefreshing은 token을 사용하는 요청이 하나가 아니라 여러개가 동시 다발적으로 일어날 수 있기 때문에, 유효하지 않은 토큰으로 보내는 모든 응답들을 멈출 수 있게끔 해주는 flag의 기능을 한다.
getStoredToken()
은 localStorage에 저장된 토큰을 가져오는 함수이다
export const getStoredToken = ():Token => {
const storedUser = localStorage.getItem(LOCALSTORAGE_USER_KEY);
return storedUser ? JSON.parse(storedUser) : null;
}
기존의 토큰을 가져온 뒤 새로운 토큰을 받아와야 한다.
const {data} = await axiosInstance.put('/member/newAccess',{},
{
headers:getNewJWTToken(token)
});
isTokenRefreshing = false;
setStoredToken(data);
axiosInstance.defaults.headers.common["X-ACCESS-TOKEN"] = data.accessToken;
member/newAccess
는 refreshToken을 headers에 담아 보내면 새로운 accessToken을 반환해주는 api
이다.
토큰을 받아오고 setStoredToken을 통해 localStorage에 새로운 accessToken을 저장하고,
axios의 default X-ACCESS-TOKEN
을 새로운 accessToken으로 교체해준다.
X-ACCESS-TOKEN
보통 Authorization: Bearer [accessToken] 형식으로 accessToken을 담아두지만 백앤드와 X-ACCESS-TOKEN으로 약속하였다.
새로운 토큰으로 다시 기존의 요청을 수행한다.
const retryOriginalRequest = new Promise((resolve) => {
addRefreshSubscriber(
(accessToken:string,refreshToken:string) => resolve(axios(originalRequest))
})
onTokenRefreshed(data.accessToken)
return retryOriginalRequest;
}
return Promise.reject(err);
}
addRefreshSubscriber는 아래와 같은 함수다
const addRefreshSubscriber = (callback:any) => {
refreshSubscribers.push(callback)
}
콜백함수를 받아서 refreshSubscribers라는 배열에 받은 콜백 함수를 push해주는 역할을 한다.
refreshSubscribers 배열의 초깃값은 빈배열이다.
let refreshSubscribers:any[] = [];
addRefreshSubscriber
를 통해 콜백함수를 넣어주는데, 그 콜백함수는 403 에러가 발생된 request들이다.
(accessToken:string,refreshToken:string) => resolve(axios(originalRequest))
403 에러가 발생된 request는 onTokenRefreshed 함수를 통해 재전송 하는데 callback에는 여러개의 함수가 쌓일 수 있으므로 map을 통해 실행하고 인자로 새로 받은 accessToken을 넣어주면서 실행한다.
const onTokenRefreshed = (accessToken:any) => {
refreshSubscribers.map((callback) => callback(accessToken))
}
새로운 요청 또한 실패했다면 Promise.reject(err)
를 통해 error를 반환한다.