feat: JWT 만료/로그아웃 시 자동 리다이렉트 처리
All checks were successful
Client CI/CD / deploy (push) Successful in 11s

- 401 응답 시 auth:unauthorized 이벤트 발생 (client.js)
- AuthContext에서 이벤트 수신 시 자동 로그아웃
- 관리자 페이지에서 인증 만료 시 메인으로, 그 외는 로그인 페이지로 이동

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-24 16:38:46 +09:00
parent 2ac2823ecc
commit e025fdfe87
3 changed files with 47 additions and 15 deletions

View File

@@ -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 <Navigate to="/login" replace />;
@@ -15,19 +36,22 @@ function AdminRoute({ children }) {
function AppRoutes() {
const { user } = useAuth();
return (
<Routes>
<Route path="/login" element={user ? <Navigate to="/" replace /> : <LoginPage />} />
<Route path="/register" element={user ? <Navigate to="/" replace /> : <RegisterPage />} />
<Route path="/" element={<HomePage />} />
<Route
path="/admin"
element={
<AdminRoute>
<AdminPage />
</AdminRoute>
}
/>
</Routes>
<>
<AuthRedirect />
<Routes>
<Route path="/login" element={user ? <Navigate to="/" replace /> : <LoginPage />} />
<Route path="/register" element={user ? <Navigate to="/" replace /> : <RegisterPage />} />
<Route path="/" element={<HomePage />} />
<Route
path="/admin"
element={
<AdminRoute>
<AdminPage />
</AdminRoute>
}
/>
</Routes>
</>
);
}

View File

@@ -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;

View File

@@ -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 (
<AuthContext.Provider value={{ user, login, logout }}>
{children}