feat: JWT 만료/로그아웃 시 자동 리다이렉트 처리
All checks were successful
Client CI/CD / deploy (push) Successful in 11s
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:
52
src/App.jsx
52
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 <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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user