Урок 07

Защищённые маршруты

Защищённые маршруты доступны только авторизованным. Остальные перенаправляются на логин.

Простой компонент-обёртка

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 для возврата
  • Роли — отдельная проверка
  • Серверная защита обязательна