Ограничение доступа к данным

В современных веб-приложениях часто возникает необходимость ограничивать доступ к определённым страницам или разделам в зависимости от состояния пользователя, его роли или других условий. React Router предоставляет удобные механизмы для реализации таких ограничений.

Использование защищённых маршрутов (Protected Routes)

Защищённый маршрут — это компонент, который проверяет условие доступа перед рендерингом целевого компонента. Если условие не выполнено, пользователь может быть перенаправлен на другую страницу (например, страницу авторизации или страницу ошибки 403).

Пример реализации защищённого маршрута:

import { Navigate, Outlet } from "react-router-dom";

const ProtectedRoute = ({ isAuthenticated, redirectPath = "/login" }) => {
  if (!isAuthenticated) {
    return <Navigate to={redirectPath} replace />;
  }
  return <Outlet />;
};

export default ProtectedRoute;

Здесь Outlet используется для вложенных маршрутов, а Navigate позволяет программно перенаправлять пользователя. isAuthenticated — логическое значение, определяющее, авторизован пользователь или нет.

Использование в маршрутах:

import { Routes, Route } from "react-router-dom";
import Dashboard from "./Dashboard";
import Login from "./Login";

<Routes>
  <Route path="/login" element={<Login />} />
  <Route element={<ProtectedRoute isAuthenticated={userLoggedIn} />}>
    <Route path="/dashboard" element={<Dashboard />} />
  </Route>
</Routes>

В данном примере доступ к /dashboard будет только у авторизованных пользователей.

Ограничение по ролям

Для более гибкого контроля можно проверять не только факт аутентификации, но и роль пользователя:

const RoleProtectedRoute = ({ userRole, allowedRoles, redirectPath = "/" }) => {
  if (!allowedRoles.includes(userRole)) {
    return <Navigate to={redirectPath} replace />;
  }
  return <Outlet />;
};

Применение:

<Route element={<RoleProtectedRoute userRole={currentUser.role} allowedRoles={['admin', 'moderator']} />}>
  <Route path="/admin" element={<AdminPanel />} />
</Route>

Таким образом, только пользователи с ролью admin или moderator смогут попасть на страницу администрирования.

Динамическое перенаправление

React Router позволяет выполнять условное перенаправление прямо в элементах маршрута:

<Route
  path="/profile"
  element={userLoggedIn ? <Profile /> : <Navigate to="/login" replace />}
/>

Это удобный способ ограничить доступ к отдельным маршрутам без создания отдельного компонента ProtectedRoute.

Ограничение доступа на уровне данных

Иногда важно не только ограничить маршрут, но и сам доступ к данным внутри компонента. Например, загрузка данных с сервера может зависеть от прав пользователя:

import { useEffect, useState } from "react";

const Dashboard = ({ userRole }) => {
  const [data, setData] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      const response = await fetch("/api/data");
      const result = await response.json();

      // фильтруем данные по роли
      if (userRole === "admin") {
        setData(result);
      } else {
        setData(result.filter(item => item.public));
      }
    };
    fetchData();
  }, [userRole]);

  return <div>{data ? <DataList data={data} /> : "Загрузка..."}</div>;
};

Здесь важное правило: проверка прав должна выполняться как на клиенте, так и на сервере. На клиенте она служит для удобства UX, на сервере — для реальной защиты данных.

Вложенные защищённые маршруты

React Router поддерживает вложенные маршруты, что позволяет строить иерархию прав доступа:

<Route element={<ProtectedRoute isAuthenticated={userLoggedIn} />}>
  <Route path="/app" element={<AppLayout />}>
    <Route path="dashboard" element={<Dashboard />} />
    <Route element={<RoleProtectedRoute userRole={currentUser.role} allowedRoles={['admin']} />}>
      <Route path="admin" element={<AdminPanel />} />
    </Route>
  </Route>
</Route>

Такой подход обеспечивает:

  • общую проверку аутентификации для всех маршрутов /app/*,
  • отдельную проверку роли для маршрута /app/admin.

Передача состояния через маршруты

Для защиты данных можно передавать состояние через state при навигации:

<Navigate to="/login" replace state={{ from: location }} />

На странице логина можно прочитать исходное место назначения:

import { useLocation, useNavigate } from "react-router-dom";

const Login = () => {
  const location = useLocation();
  const navigate = useNavigate();
  const from = location.state?.from?.pathname || "/";

  const handleLogin = () => {
    authenticateUser();
    navigate(from, { replace: true });
  };
};

Это позволяет после успешной авторизации возвращать пользователя на страницу, которую он пытался открыть.

Итоговые принципы ограничения доступа

  1. ProtectedRoute и RoleProtectedRoute — основной инструмент для защиты маршрутов.
  2. Navigate используется для программного перенаправления.
  3. Outlet обеспечивает рендеринг вложенных маршрутов.
  4. Проверки должны дублироваться на сервере для безопасности данных.
  5. Вложенные маршруты позволяют создавать иерархии прав доступа.
  6. Использование state при навигации помогает корректно возвращать пользователя после авторизации.

Эти подходы обеспечивают гибкое управление доступом к страницам и данным, позволяя строить безопасные и масштабируемые приложения на React Router.