Настройка защищённых маршрутов
Итак, представьте себе: каждый раз, когда пользователь пытается попасть на определённую страницу (например, /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;
Что здесь происходит?
isAuthenticated: проверяет наличие токена вlocalStorage. Здесь мы предполагаем, что токен надёжно сохранён (хотя в реальных приложениях нужно учитывать риск XSS).Outlet: используется для рендера вложенных маршрутов.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;
Теперь, перед рендерингом защищённых маршрутов, мы убедимся, что токен проверен сервером.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ