diff --git a/backend-node/src/routes/authRoutes.ts b/backend-node/src/routes/authRoutes.ts index 7ed87a06..688b6cd7 100644 --- a/backend-node/src/routes/authRoutes.ts +++ b/backend-node/src/routes/authRoutes.ts @@ -2,7 +2,6 @@ // Phase 2-1B: 핵심 인증 API 구현 import { Router } from "express"; -import { checkAuthStatus } from "../middleware/authMiddleware"; import { AuthController } from "../controllers/authController"; const router = Router(); @@ -12,7 +11,7 @@ const router = Router(); * 인증 상태 확인 API * 기존 Java ApiLoginController.checkAuthStatus() 포팅 */ -router.get("/status", checkAuthStatus); +router.get("/status", AuthController.checkAuthStatus); /** * POST /api/auth/login diff --git a/frontend/hooks/useAuth.ts b/frontend/hooks/useAuth.ts index d03aab29..9aab8e13 100644 --- a/frontend/hooks/useAuth.ts +++ b/frontend/hooks/useAuth.ts @@ -161,13 +161,14 @@ export const useAuth = () => { setLoading(true); const token = TokenManager.getToken(); - if (!token || TokenManager.isTokenExpired(token)) { - AuthLogger.log("AUTH_CHECK_FAIL", `refreshUserData: 토큰 ${!token ? "없음" : "만료됨"}`); + if (!token) { + AuthLogger.log("AUTH_CHECK_FAIL", "refreshUserData: 토큰 없음"); setUser(null); setAuthStatus({ isLoggedIn: false, isAdmin: false }); setLoading(false); return; } + // 만료된 토큰이라도 apiClient 요청 인터셉터가 자동 갱신하므로 여기서 차단하지 않음 AuthLogger.log("AUTH_CHECK_START", "refreshUserData: API로 인증 상태 확인 시작"); @@ -177,6 +178,10 @@ export const useAuth = () => { }); try { + // /auth/me 성공 = 인증 확인 완료. /auth/status는 보조 정보(isAdmin)만 참조 + // 두 API를 Promise.all로 호출 시, 토큰 만료 타이밍에 따라 + // /auth/me는 401→갱신→성공, /auth/status는 200 isAuthenticated:false를 반환하는 + // 레이스 컨디션이 발생할 수 있으므로, isLoggedIn 판단은 /auth/me 성공 여부로 결정 const [userInfo, authStatusData] = await Promise.all([fetchCurrentUser(), checkAuthStatus()]); if (userInfo) { @@ -184,19 +189,12 @@ export const useAuth = () => { const isAdminFromUser = userInfo.userId === "plm_admin" || userInfo.userType === "ADMIN"; const finalAuthStatus = { - isLoggedIn: authStatusData.isLoggedIn, + isLoggedIn: true, isAdmin: authStatusData.isAdmin || isAdminFromUser, }; setAuthStatus(finalAuthStatus); AuthLogger.log("AUTH_CHECK_SUCCESS", `사용자: ${userInfo.userId}, 인증: ${finalAuthStatus.isLoggedIn}`); - - if (!finalAuthStatus.isLoggedIn) { - AuthLogger.log("AUTH_CHECK_FAIL", "API 응답에서 비인증 상태 반환 → 토큰 제거"); - TokenManager.removeToken(); - setUser(null); - setAuthStatus({ isLoggedIn: false, isAdmin: false }); - } } else { AuthLogger.log("AUTH_CHECK_FAIL", "userInfo 조회 실패 → 토큰 기반 임시 인증 유지 시도"); try { @@ -412,18 +410,19 @@ export const useAuth = () => { const token = TokenManager.getToken(); - if (token && !TokenManager.isTokenExpired(token)) { - AuthLogger.log("AUTH_CHECK_START", `초기 인증 확인: 유효한 토큰 존재 (경로: ${window.location.pathname})`); + if (token) { + // 유효/만료 모두 refreshUserData로 처리 + // apiClient 요청 인터셉터가 만료 토큰을 자동 갱신하므로 여기서 삭제하지 않음 + const isExpired = TokenManager.isTokenExpired(token); + AuthLogger.log( + "AUTH_CHECK_START", + `초기 인증 확인: 토큰 ${isExpired ? "만료됨 → 갱신 시도" : "유효"} (경로: ${window.location.pathname})`, + ); setAuthStatus({ isLoggedIn: true, isAdmin: false, }); refreshUserData(); - } else if (token && TokenManager.isTokenExpired(token)) { - AuthLogger.log("TOKEN_EXPIRED_DETECTED", `초기 확인 시 만료된 토큰 발견 → 정리 (경로: ${window.location.pathname})`); - TokenManager.removeToken(); - setAuthStatus({ isLoggedIn: false, isAdmin: false }); - setLoading(false); } else { AuthLogger.log("AUTH_CHECK_FAIL", `초기 확인: 토큰 없음 (경로: ${window.location.pathname})`); setAuthStatus({ isLoggedIn: false, isAdmin: false }); diff --git a/frontend/lib/api/client.ts b/frontend/lib/api/client.ts index 2338ad63..bd935b63 100644 --- a/frontend/lib/api/client.ts +++ b/frontend/lib/api/client.ts @@ -329,6 +329,11 @@ apiClient.interceptors.request.use( const newToken = await refreshToken(); if (newToken) { config.headers.Authorization = `Bearer ${newToken}`; + } else { + // 갱신 실패 시 인증 없는 요청을 보내면 TOKEN_MISSING 401 → 즉시 redirectToLogin 연쇄 장애 + // 요청 자체를 차단하여 호출부의 try/catch에서 처리하도록 함 + authLog("TOKEN_REFRESH_FAIL", `요청 인터셉터에서 갱신 실패 → 요청 차단 (${config.url})`); + return Promise.reject(new Error("TOKEN_REFRESH_FAILED")); } } }