JavaRush /Курсы /Модуль 3: React /Создание защищённых маршрутов с JWT и React Router

Создание защищённых маршрутов с JWT и React Router

Модуль 3: React
15 уровень , 9 лекция
Открыта

Настройка защищённых маршрутов

Итак, представьте себе: каждый раз, когда пользователь пытается попасть на определённую страницу (например, /dashboard), нам нужно проверить, авторизован ли пользователь. Если токен не найден или он недействителен, мы перенаправляем пользователя на страницу логина /login. Если пользователь прошёл проверку, предоставляем ему доступ к запрашиваемому ресурсу. Это всё и есть "защита маршрутов".

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

1. Создание компонента ProtectedRoute

В этом компоненте мы проверим, есть ли у пользователя действительный JWT. Если токен отсутствует или истёк, мы отправим пользователя на страницу логина.

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

// Функция для проверки токена (в идеале, эта логика должна быть в вашем AuthService)
const isAuthenticated = (): boolean => {
  const token = localStorage.getItem("jwtToken");
  // Простая проверка на наличие токена, здесь же можно добавить проверку на истечение
  return !!token;
};

const ProtectedRoute: React.FC = () => {
  return isAuthenticated() ? <Outlet /> : <Navigate to="/login" replace />;
};

export default ProtectedRoute;

Что здесь происходит?

  1. isAuthenticated: проверяет наличие токена в localStorage. Здесь мы предполагаем, что токен надёжно сохранён (хотя в реальных приложениях нужно учитывать риск XSS).
  2. Outlet: используется для рендера вложенных маршрутов.
  3. Navigate: перенаправляет пользователя на /login, если он не аутентифицирован.

Теперь наш ProtectedRoute готов. Следующим шагом будем его использовать в React Router.

2. Настройка маршрутов с использованием ProtectedRoute

Теперь давайте обновим конфигурацию маршрутов, чтобы включить защищённые маршруты. Предположим, в нашем приложении есть два маршрута: /dashboard (требует аутентификации) и /login (открыт для всех).

import React from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import ProtectedRoute from "./components/ProtectedRoute";
import Dashboard from "./pages/Dashboard";
import Login from "./pages/Login";

const App: React.FC = () => {
  return (
    <Router>
      <Routes>
        <Route path="/login" element={<Login />} />
        <Route path="/dashboard" element={<ProtectedRoute />}>
          <Route path="" element={<Dashboard />} />
        </Route>
      </Routes>
    </Router>
  );
};

export default App;

Как это работает?

  • ProtectedRoute проверяет, есть ли токен.
  • Если проверка пройдена, он рендерит дочерний компонент Dashboard.
  • Если проверка не пройдена, пользователь перенаправляется на /login.

Редирект и доступ к маршрутам

1. Перенаправление после успешного логина

После успешного входа в систему нам нужно перенаправить пользователя на защищённый маршрут, например, /dashboard. Для этого можно использовать хук useNavigate из react-router-dom.

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

const Login: React.FC = () => {
  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");
  const navigate = useNavigate();

  const handleLogin = async (e: React.FormEvent) => {
    e.preventDefault();

    // Здесь отправляем данные на сервер и получаем токен
    const token = "dummyJwtToken"; // замените на реальный API вызов
    localStorage.setItem("jwtToken", token);

    // Перенаправляем пользователя на защищённый маршрут
    navigate("/dashboard");
  };

  return (
    <form onSubmit={handleLogin}>
      <input
        type="text"
        placeholder="Username"
        value={username}
        onChange={(e) => setUsername(e.target.value)}
      />
      <input
        type="password"
        placeholder="Password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
      />
      <button type="submit">Login</button>
    </form>
  );
};

export default Login;

2. Перенаправление неаутентифицированного пользователя на страницу логина

Мы реализовали это с помощью компонента ProtectedRoute. Если токен недействителен, пользователь автоматически перенаправляется на /login. Однако иногда бывает полезно дать пользователю сообщение: "Вы не авторизованы, пожалуйста, войдите". Для этого можно использовать state у компонента Navigate.

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

const ProtectedRoute: React.FC = () => {
  const isAuthenticated = localStorage.getItem("jwtToken");

  return isAuthenticated ? (
    <Outlet />
  ) : (
    <Navigate to="/login" state={{ message: "Please log in to continue" }} />
  );
};

На странице логина можно отобразить это сообщение:

import React from "react";
import { useLocation } from "react-router-dom";

const Login: React.FC = () => {
  const location = useLocation();
  const message = location.state?.message;

  return (
    <div>
      {message && <p>{message}</p>}
      {/* Ваши поля для логина */}
    </div>
  );
};

export default Login;

Распространённые ошибки и способы их избежать

Часто при создании защищённых маршрутов разработчики сталкиваются с рядом проблем. Например, токен может быть недействительным, но сохранён в localStorage. Для обработки такой ситуации рекомендуется проверять токен на сервере перед предоставлением доступа. Это может быть реализовано через API-запросы на сервер.

Кроме того, иногда приложение может находиться в промежуточном состоянии, когда ещё не известно, авторизован пользователь или нет (например, при обновлении страницы). В этом случае нужно добавлять механизм загрузки loading перед отображением маршрутов.

Добавление состояния загрузки

Допустим, токен необходимо проверить на сервере перед рендерингом защищённого компонента. Для этого добавим "загрузочный экран".

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

const ProtectedRoute: React.FC = () => {
  const [isLoading, setIsLoading] = useState(true);
  const [isAuth, setIsAuth] = useState<boolean | null>(null);

  useEffect(() => {
    const checkAuth = async () => {
      const token = localStorage.getItem("jwtToken");
      if (!token) return setIsAuth(false);

      // Проверка токена на сервере
      const response = await fetch("/api/auth/validate", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${token}`,
        },
      });
      setIsAuth(response.ok);
      setIsLoading(false);
    };

    checkAuth();
  }, []);

  if (isLoading) return <div>Loading...</div>;
  return isAuth ? <Outlet /> : <Navigate to="/login" />;
};

export default ProtectedRoute;

Теперь, перед рендерингом защищённых маршрутов, мы убедимся, что токен проверен сервером.

2
Задача
Модуль 3: React, 15 уровень, 9 лекция
Недоступна
Реализация компонента ProtectedRoute
Реализация компонента ProtectedRoute
3
Опрос
Автоматическое обновление токена, 15 уровень, 9 лекция
Недоступен
Автоматическое обновление токена
Автоматическое обновление токена
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ