Защищённые маршруты
Защищённые маршруты доступны только авторизованным. Остальные перенаправляются на логин.
Простой компонент-обёртка
import { Navigate } from 'react-router-dom';
function RequireAuth({ children }: { children: React.ReactNode }) {
const { user } = useAuth();
if (!user) {
return <Navigate to="/login" replace />;
}
return children;
}
Использование
<Routes>
<Route path="/login" element={<Login />} />
<Route
path="/dashboard"
element={
<RequireAuth>
<Dashboard />
</RequireAuth>
}
/>
<Route
path="/settings"
element={
<RequireAuth>
<Settings />
</RequireAuth>
}
/>
</Routes>
Layout-обёртка
Удобнее обернуть группу маршрутов:
function RequireAuth() {
const { user } = useAuth();
const location = useLocation();
if (!user) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
return <Outlet />;
}
<Routes>
<Route path="/login" element={<Login />} />
<Route element={<RequireAuth />}>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Route>
</Routes>
Возврат на исходный маршрут
При редиректе сохраняем from. После логина возвращаемся:
function Login() {
const navigate = useNavigate();
const location = useLocation();
const from = location.state?.from?.pathname ?? '/dashboard';
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
await login(credentials);
navigate(from, { replace: true });
}
}
Роли
Не только авторизация, но и роль:
function RequireRole({ role, children }: { role: string; children: React.ReactNode }) {
const { user } = useAuth();
if (!user) return <Navigate to="/login" replace />;
if (user.role !== role) return <Navigate to="/forbidden" replace />;
return children;
}
<Route
path="/admin"
element={
<RequireRole role="admin">
<AdminPanel />
</RequireRole>
}
/>
Защита на сервере
Клиентская защита — только UX. Данные должны быть защищены на сервере. Если пользователь обойдёт роутер и запросит /api/admin/users — сервер должен вернуть 403.
Ожидание загрузки пользователя
Если данные пользователя загружаются асинхронно, покажи спиннер:
function RequireAuth() {
const { user, isLoading } = useAuth();
if (isLoading) return <Spinner />;
if (!user) return <Navigate to="/login" replace />;
return <Outlet />;
}
Итоги
- Защита — компонент-обёртка
<Navigate>для редиректа- Layout с
Outletдля групп - Сохранение
fromдля возврата - Роли — отдельная проверка
- Серверная защита обязательна