Как реализовать защищенные маршруты в React?react-30

Защищенные маршруты (Protected Routes) — это механизм ограничения доступа к определенным разделам приложения для неавторизованных пользователей. Рассмотрим современные подходы к реализации.

1. Базовый вариант

Создаем компонент-обертку для проверки авторизации:

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

const ProtectedRoute = ({ isAllowed, redirectPath = '/login', children }) => {
  if (!isAllowed) {
    return <Navigate to={redirectPath} replace />;
  }

  return children ? children : <Outlet />;
};

Использование:

<Route
  path="/dashboard"
  element={<ProtectedRoute isAllowed={isAuthenticated} />}
>
  <Route index element={<Dashboard />} />
  <Route path="settings" element={<Settings />} />
</Route>

2. Расширенный вариант

Для систем с ролевой моделью:

const RoleProtectedRoute = ({ roles, userRole, children }) => {
  const isAllowed = roles.includes(userRole);

  if (!isAllowed) {
    return <Navigate to="/not-authorized" replace />;
  }

  return children ? children : <Outlet />;
};

Использование:

<Route
  path="/admin"
  element={
    <ProtectedRoute isAllowed={isAuthenticated}>
      <RoleProtectedRoute roles={['admin', 'superadmin']} userRole={currentUser.role} />
    </ProtectedRoute>
  }
>
  <Route index element={<AdminPanel />} />
</Route>

3. Интеграция с контекстом/Redux

Лучше всего работает с глобальным состоянием:

const AuthProtectedRoute = () => {
  const { isAuthenticated, isLoading } = useAuth(); // Ваш хук/контекст

  if (isLoading) {
    return <LoadingSpinner />;
  }

  return isAuthenticated ? <Outlet /> : <Navigate to="/login" replace />;
};

4. Защищенные маршруты с хранением истории

Чтобы запомнить, откуда пришел пользователь:

const ProtectedRouteWithHistory = () => {
  const location = useLocation();
  const { isAuthenticated } = useAuth();

  if (!isAuthenticated) {
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  return <Outlet />;
};

На странице логина:

const location = useLocation();
const navigate = useNavigate();

const handleLogin = () => {
  login().then(() => {
    navigate(location.state?.from || '/', { replace: true });
  });
};

5. Комплексный пример

Полноценная защита маршрутов с проверкой токена:

const TokenProtectedRoute = () => {
  const [isValid, setIsValid] = useState(null);
  const { checkAuth } = useAuth();

  useEffect(() => {
    const verifyToken = async () => {
      try {
        await checkAuth(); // Проверка access/refresh токенов
        setIsValid(true);
      } catch {
        setIsValid(false);
      }
    };

    verifyToken();
  }, []);

  if (isValid === null) return <LoadingPage />;

  return isValid ? <Outlet /> : <Navigate to="/login" replace />;
};

6. Редирект для авторизованных пользователей

Чтобы авторизованные пользователи не попадали на страницы логина/регистрации:

const PublicRoute = ({ children }) => {
  const { isAuthenticated } = useAuth();

  return isAuthenticated ? <Navigate to="/" replace /> : children;
};

Использование:

<Route path="/login" element={<PublicRoute><LoginPage /></PublicRoute>} />

Резюмируем

  1. Основные подходы:

    • Компонент-обертка (ProtectedRoute)
    • Проверка ролей (RoleProtectedRoute)
    • Интеграция с глобальным состоянием
    • Работа с JWT-токенами
  2. Ключевые элементы:

    • Navigate для перенаправления
    • Outlet для вложенных маршрутов
    • Сохранение истории навигации
  3. Рекомендации:

    • Всегда используйте replace при редиректах
    • Добавляйте обработку загрузки
    • Разделяйте проверку авторизации и ролей
    • Для сложных систем используйте middleware-подход

Пример полной реализации:

<Route element={<TokenProtectedRoute />}>
  <Route path="/profile" element={<Profile />} />
  <Route path="/settings" element={<Settings />} />
</Route>

<Route element={<RoleProtectedRoute roles={['admin']} />}>
  <Route path="/admin" element={<AdminPanel />} />
</Route>

<Route path="/login" element={<PublicRoute><Login /></PublicRoute>} />