로그인 api 요청 시 access token과 refresh token은 모든 api 로직에 공유되고 있다.
access token이 만료되면 refresh token을 통해 재발급을 받게 되는데, 재발급 로직은 어디에 위치시켜줘야 하는걸까? 🤔
✔ 모든 api 요청은 access token을 쿠키를 통해 공유하고 있는데, 그렇기 때문에 재발급 받는 엑세스 토큰은 가장 루트에 위치시켜야 한다.
✔ 그래야 모든 로직에서 쿠키를 통해 엑세스 토큰 공유가 가능하다.
현재 프로젝트에서 리액트 쿼리(v5)로 상태 관리를 해주고 있었기 때문에 모든 로직에 전달이 가능하게 queryClient의 default 옵션에 엑세스 토큰 재발급 로직을 넣어줄 것이다.
🔺 access token이 만료되면 일어나는 현상
-> 401에러가 나오고, 로그인 화면으로 돌아가게 된다.
queryClient의 queryCache 옵션을 이용하면, query 동작에서의 에러 핸들링이 가능하다.
🎁 코드
const Providers = ({ children }: { children: ReactNode }) => {
const router = useRouter();
const pathname = usePathname();
const queryClient = useRef<QueryClient>();
if (!queryClient.current) { // 🟢 처음 마운트 됐을 때의 조건 (데이터가 비어있을 때)
queryClient.current = new QueryClient({
queryCache: new QueryCache({
onError: (e) => {
const err = e as unknown as AxiosError;
const refresh_token = localStorage.getItem('refresh_token');
if (err.response.status === 401 && pathname !== '/login') {
getAccessToken(refresh_token) // 🎉 재발급 요청 함수
}
},
}),
});
}
return (
<QueryClientProvider client = {queryClient.current}>
{children}
</QueryClientProvider>
💥 위 코드의 문제점
-> pathname이 처음 설정하고, onErro 안에서 동작하게 되면 고정된 값을 유지해서 pathname이 변하지 않는다. 그렇기 때문에 pathname이 바뀔 때마다 다시 검토할 수 있게 queryCache 옵션 자체를 빼주었다.
다행히, queryCache의 config 속성을 활용하면 따로 빼줄 수 있다.
🎁 코드
const Providers = ({ children }: { children: ReactNode }) => {
const router = useRouter();
const pathname = usePathname();
const queryClient = useRef<QueryClient>(); // 🔺 이 설정을 안해주면, 해당 컴포넌트 이외에 queryClient 조건이 적용되지 않을 것이다.
const queryCache = useRef<QueryCache>(); // 쿼리 캐시의 동작을 읽기 위해
if (!queryCache.current) {
queryCache.current = new QueryCache();
}
if (!queryClient.current) {
queryClient.current = new QueryClient({
defaultOptions: {
queries: {
retry: 1, //재시도 횟수 - default 3
},
},
queryCache: queryCache.current,
});
}
queryCache.current.config = useMemo(
() => ({
onError: (e) => {
const err = e as unknown as AxiosError;
// 🟡 login 401 -> 로그인 정보가 틀림(ID/PW), refresh 401 -> refresh 만료
// 🔴 axios는 response 내부에 에러 상태(status) 존재
if (err.response?.status == 401 && pathname !== '/login') {
const refresh_token = localStorage.getItem('refresh_token');
getAccessToken(refresh_token)
.then(() => {
queryClient.current?.invalidateQueries();
})
.catch(() => {
//refresh 요청도 실패하면 login 창으로 이동
queryClient.current?.clear();
router.replace('/login');
});
}
},
}),
[pathname, router],
);
return (
<QueryClientProvider client={queryClient.current}>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
};
✔ 바꾼 코드로 이제 실행을 하면, 모든 api 요청에는 access token이 만료되어도 재발급 받은 토큰이 쿠키를 통해 포함되기 때문에 로그인 유지가 가능하다.