issue #33
All checks were successful
Test CI / build (push) Successful in 18s

- Access 토큰 만료시 Refresh 토큰으로 Access 토큰 재갱신 요청 로직 구현 중
This commit is contained in:
2025-12-01 22:35:29 +09:00
parent 45dc4cbaaa
commit 49ca9b9ae3
4 changed files with 88 additions and 10 deletions

View File

@@ -0,0 +1,6 @@
import { BaseResponse } from "../BaseResponse";
export class RefreshAccessTokenResponse extends BaseResponse {
accessToken!: string;
refreshToken!: string;
}

View File

@@ -32,7 +32,7 @@ export default function Layout() {
> >
<SideBar /> <SideBar />
<div className="flex flex-col w-full h-full"> <div className="flex flex-col w-full h-full">
{ authData?.isLogedIn ? <Header /> : null} { authData ? <Header /> : null}
{/* <Header /> */} {/* <Header /> */}
<Outlet /> <Outlet />
</div> </div>

View File

@@ -6,10 +6,15 @@ import type {
AxiosResponse, AxiosResponse,
InternalAxiosRequestConfig, InternalAxiosRequestConfig,
} from "axios"; } from "axios";
import { useAuthStore } from '@/store/authStore';
import type { RefreshAccessTokenResponse } from '@/data/response/account/RefreshAccessTokenResponse';
export class BaseNetwork { export class BaseNetwork {
protected instance: AxiosInstance; protected instance: AxiosInstance;
private isRefreshing = false;
private refreshQueue: Array<(token:string) => void> = [];
constructor() { constructor() {
this.instance = axios.create({ this.instance = axios.create({
baseURL: import.meta.env.VITE_API_URL || "http://localhost:3000", baseURL: import.meta.env.VITE_API_URL || "http://localhost:3000",
@@ -48,20 +53,86 @@ export class BaseNetwork {
this.instance.interceptors.response.use( this.instance.interceptors.response.use(
(response: AxiosResponse) => response, (response: AxiosResponse) => response,
(error: AxiosError) => { (error: AxiosError) => {
const message = const status = error.response?.status;
(error.response?.data as any)?.message || const errorCode = (error.response?.data as any)?.code;
error.message || const originalRequest = error.config as AxiosRequestConfig & {
"Unknown error"; _retry?: boolean;
}
return Promise.reject({ if (
status: error.response?.status, status === 401
message, && errorCode === 'AccessTokenExpired'
raw: error, && !originalRequest._retry
}); ) {
originalRequest._retry = true;
return this.handleRefreshToken(originalRequest);
}
return Promise.reject(error);
} }
); );
} }
private async handleRefreshToken(originalRequest: AxiosRequestConfig) {
const authData = useAuthStore.getState().authData;
const refreshToken = authData?.refreshToken;
if (!authData || !refreshToken) {
useAuthStore.getState().logout();
return Promise.reject("no refresh token");
}
if (this.isRefreshing) {
return new Promise((resolve) => {
this.refreshQueue.push((newToken: string) => {
originalRequest.headers = {
...originalRequest.headers,
Authorization: `Bearer ${newToken}`
};
resolve(this.instance(originalRequest));
})
})
}
this.isRefreshing = true;
try {
const response = await this.get<RefreshAccessTokenResponse>(
'/account/refresh-access-token',
{
headers: {
Authorization: `Bearer ${refreshToken}`
}
}
)
const newAccessToken = response.data.accessToken;
const newRefreshToken = response.data.refreshToken;
useAuthStore.getState().login({
...authData,
accessToken: newAccessToken,
refreshToken: newRefreshToken
});
this.refreshQueue.forEach((cb) => cb(newAccessToken));
this.refreshQueue = [];
originalRequest.headers = {
...originalRequest.headers,
Authorization: `Bearer ${newAccessToken}`,
};
return this.instance(originalRequest);
} catch (err) {
useAuthStore.getState().logout();
window.location.href = '/login?expired=1'
return Promise.reject(err);
} finally {
this.isRefreshing = false;
}
}
/** /**
* 기본 CRUD 메서드 * 기본 CRUD 메서드
*/ */

View File

@@ -4,6 +4,7 @@ import { create } from 'zustand';
interface AuthStoreProps { interface AuthStoreProps {
authData: AuthData | undefined; authData: AuthData | undefined;
login: (data: AuthData) => void; login: (data: AuthData) => void;
logout: () => void;
} }
export const useAuthStore = create<AuthStoreProps>((set) => ({ export const useAuthStore = create<AuthStoreProps>((set) => ({