From e025fdfe8778be3c35aa2d7e79c1643200db00d4 Mon Sep 17 00:00:00 2001 From: tolelom <98kimsungmin@naver.com> Date: Tue, 24 Feb 2026 16:38:46 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20JWT=20=EB=A7=8C=EB=A3=8C/=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=95=84=EC=9B=83=20=EC=8B=9C=20=EC=9E=90=EB=8F=99=20?= =?UTF-8?q?=EB=A6=AC=EB=8B=A4=EC=9D=B4=EB=A0=89=ED=8A=B8=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 401 응답 시 auth:unauthorized 이벤트 발생 (client.js) - AuthContext에서 이벤트 수신 시 자동 로그아웃 - 관리자 페이지에서 인증 만료 시 메인으로, 그 외는 로그인 페이지로 이동 Co-Authored-By: Claude Sonnet 4.6 --- src/App.jsx | 52 +++++++++++++++++++++++++++---------- src/api/client.js | 3 +++ src/context/AuthContext.jsx | 7 ++++- 3 files changed, 47 insertions(+), 15 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index a17dd8e..dafa0ce 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,10 +1,31 @@ -import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; +import { BrowserRouter, Routes, Route, Navigate, useNavigate, useLocation } from 'react-router-dom'; +import { useEffect, useRef } from 'react'; import { AuthProvider, useAuth } from './context/AuthContext'; import LoginPage from './pages/LoginPage'; import RegisterPage from './pages/RegisterPage'; import HomePage from './pages/HomePage'; import AdminPage from './pages/AdminPage'; +function AuthRedirect() { + const { user } = useAuth(); + const navigate = useNavigate(); + const location = useLocation(); + const prevUserRef = useRef(user); + + useEffect(() => { + if (prevUserRef.current && !user) { + if (location.pathname.startsWith('/admin')) { + navigate('/', { replace: true }); + } else { + navigate('/login', { replace: true }); + } + } + prevUserRef.current = user; + }, [user, navigate, location.pathname]); + + return null; +} + function AdminRoute({ children }) { const { user } = useAuth(); if (!user) return ; @@ -15,19 +36,22 @@ function AdminRoute({ children }) { function AppRoutes() { const { user } = useAuth(); return ( - - : } /> - : } /> - } /> - - - - } - /> - + <> + + + : } /> + : } /> + } /> + + + + } + /> + + ); } diff --git a/src/api/client.js b/src/api/client.js index 72680a8..1ab9630 100644 --- a/src/api/client.js +++ b/src/api/client.js @@ -6,6 +6,9 @@ export async function apiFetch(path, options = {}) { if (token) headers['Authorization'] = `Bearer ${token}`; const res = await fetch(BASE + path, { ...options, headers }); + if (res.status === 401) { + window.dispatchEvent(new Event('auth:unauthorized')); + } if (!res.ok) { const err = new Error(res.statusText); err.status = res.status; diff --git a/src/context/AuthContext.jsx b/src/context/AuthContext.jsx index 8349209..867ccfd 100644 --- a/src/context/AuthContext.jsx +++ b/src/context/AuthContext.jsx @@ -1,4 +1,4 @@ -import { createContext, useContext, useState, useCallback } from 'react'; +import { createContext, useContext, useState, useCallback, useEffect } from 'react'; import { login as apiLogin } from '../api/auth'; const AuthContext = createContext(null); @@ -26,6 +26,11 @@ export function AuthProvider({ children }) { setUser(null); }, []); + useEffect(() => { + window.addEventListener('auth:unauthorized', logout); + return () => window.removeEventListener('auth:unauthorized', logout); + }, [logout]); + return ( {children}