From aebfa149846a9719b34237b8dce84f0ac73dcf46 Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Fri, 3 Apr 2026 06:58:36 +0900 Subject: [PATCH] fix: don't intercept 401 on login/refresh endpoints for token refresh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Login 401 (TOTP required) was being caught by the refresh interceptor, masking the actual error detail with "인증이 만료되었습니다". Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/src/lib/api.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 1b71f89..59014dc 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -101,8 +101,9 @@ export async function api( credentials: 'include', }); - // 401 → refresh 1회 시도 - if (res.status === 401 && accessToken) { + // 401 → refresh 1회 시도 (로그인/리프레시 엔드포인트는 제외) + const isAuthEndpoint = path.startsWith('/auth/login') || path.startsWith('/auth/refresh'); + if (res.status === 401 && accessToken && !isAuthEndpoint) { try { await handleTokenRefresh(); headers['Authorization'] = `Bearer ${accessToken}`; @@ -113,11 +114,12 @@ export async function api( }); if (!retryRes.ok) { const err = await retryRes.json().catch(() => ({ detail: 'Unknown error' })); - throw { status: retryRes.status, detail: err.detail || retryRes.statusText }; + throw { status: retryRes.status, detail: err.detail || retryRes.statusText } as ApiError; } return retryRes.json(); - } catch { - throw { status: 401, detail: '인증이 만료되었습니다' }; + } catch (e) { + if ((e as ApiError).detail) throw e; + throw { status: 401, detail: '인증이 만료되었습니다' } as ApiError; } }