Axios는 생각한 거 이상으로 다양한 기능이 있는 것 같다!
굉장히 편리함,,,
Axios interceptors를 활용하면 서버에 요청을 하기 전에 무언가를 처리할 수도 있고,
요청을 하고 나서 응답을 받았을 때도 미리 interceptors에서 무언가를 처리해줄 수 있다. (에러처리도 가능!)
그래서 오늘은 사이드 프로젝트를 현재 진행하면서, Axios interceptors를 활용하여 'Access Token'이 만료가 되었을 때, 자동으로 새로운 토큰을 발급받아서 재요청을 하는 법을 공유하고자 합니다 -!
그 전에 먼저 알게 된 것을 공유하자면,
프론트 쪽에서도 토큰의 만료시간을 확인할 수 있다는 것이다!
크게 javascript 내장함수인 'atob'를 활용하거나, 혹은 'jwt-decode'라는 라이브러리를 활용하는 두 가지 방법이 있다.
참고한 자료 : https://www.bezkoder.com/handle-jwt-token-expiration-react/
나는 위의 자료를 참고하여 'atob'를 활용하여 토큰을 디코딩 해주었다.
const parseJwt = (token: string | null) => {
if (token) return JSON.parse(atob(token.split('.')[1]));
};
export const AuthVerify = () => {
const decodedAccess = parseJwt(access);
const decodedRefresh = parseJwt(refresh);
if (decodedAccess.exp * 1000 < Date.now()) {
return 'Access Token Expired';
}
if (decodedRefresh.exp * 1000 < Date.now()) {
return 'Refresh Token Expired';
}
return true;
};
위의 AuthVeirify 함수는 interceptors에서도 사용할 예정!
import {access, refresh } from getToken;
const customHttp = axios.create({
baseURL: BASE_URL,
timeout: 8000,
headers: {
'Content-Type': `application/json`,
access,
refresh
},
});
위의 custom axios 는 put, delete, post 요청을 할 때 사용하기 위한 axios이다. (get을 할때는 따로 headers에 토큰을 넣어줄 필요가 없어서 위의 axios를 활용하지 않음)
🔖 참고)
참고로, 위의 axios를 직접 사용하는 법은 다음과 같다.
const EditForm = async (values: FormatModel) => {
try {
const res = await customHttp.put(`/api/posts/${id}`, values);
if (res.status === 200 && AuthVerify()) {
message.success('해당 게시글이 수정되었습니다.');
const id = (res.data as { id: number }).id;
navigate(`/project/${id}`);
}
} catch (err) {
console.log(err);
};
위와 같이 custom axios + 원하는 http method 붙혀주면 된다!
export const renewAccessToken = (token: string) => {
localStorage.removeItem('accessToken');
setAccessToken(token);
};
export const onFulfilled = async (res: AxiosResponse) => {
if (AuthVerify() === 'Access Token Expired') {
const { access: newAccess } = res.data;
renewAccessToken(newAccess);
res.config.headers = {
'Content-Type': `application/json`,
refresh: refresh ?? '',
access: newAccess,
};
return await axios(res.config);
} else return res;
};
customHttp.interceptors.response.use(onFulfilled);
✔️ 참고로, refresh token은 유효하고, 이미 access token은 만료된 상태라면, 서버에서 자동으로 새로운 토큰을 발급받아준다. (우리가 진행한 사이드 프로젝트에서는 그렇게 진행하였음! )
그래서 response를 받으면, 그 안에 담겨있는 새로운 토큰을 localstorage에 다시 저장해주고, response의 config내의 headers안에 새로운 토큰을 다시 갈아끼워주고,
새로운 axios(res.config)를 return 함으로써 다시 해당 요청을 하는 것이다!
👉 이렇게하면, 위의 과정이 필요한 모든 함수들의 response에 일일히 위의 코드들을 적어줄 필요도 없고, 한번에 처리가 가능하다! 또한 함수를 함수 내에서 따로 재요청을 해주지 않아도, interceptors에서 알아서 걸러서 처리를 해주니 매우 편리함!
👉 interceptors의 두번째 인자에 넣어주면 된다.
ex)
const onRejected = (err) => {return Promise.reject(err)}
customHttp.interceptors.response.use(onFulfilled, onRejected);
response 처리하는 법과 동일! 다만 요청하기 전의 처리과정이므로, response가 아닌 config를 받아온다는 차이가 있음!
이 때 headers에 토큰을 넣어줄 수도 있음!
customHttp.interceptors.request.use(
async (config: AxiosRequestConfig) => {
if (token) {
config.headers['Content-Type'] = 'application/json';
config.headers.Authorization = token;
}
return config;
},
err => {
return Promise.reject(err);
},
);
처음에는 모르고 저기 위의 onFulfilled라는 함수 내에 useNavigate hook 넣었다가, 오류 먹고 아 왜안돼,,, 왜이러나 싶었는데 생각해보니, hook 내의 hook을 넣어주면 안된다는 오류가 있었다...⭐️ 그래서 처음에 엄청 헤맸었는데 알고보니 엄청 간편하고 편리했던 아이,, 첨에 욕해서 미안하다(?)