Задача: создать защищённые маршруты
В защищённых маршрутах нас будет интересовать:
- Проверка, авторизован ли пользователь.
- Перенаправление неавторизованных пользователей на страницу логина.
- Типизация данных о пользователе и логики проверки авторизации.
Стартовый код
Создадим проект и убедимся, что у нас установлены все необходимые зависимости:
npx create-react-app protected-routes --template typescript
cd protected-routes
npm install react-router-dom
Также создадим имитацию сервиса авторизации. Например, сделаем простой токен для проверки:
// src/auth/AuthService.ts
interface User {
name: string;
role: 'admin' | 'user';
}
// Импровизированный токен
const fakeToken = '123secureToken';
export const AuthService = {
isAuthenticated: false,
user: null as User | null,
login(token: string): boolean {
if (token === fakeToken) {
this.isAuthenticated = true;
this.user = { name: 'John Doe', role: 'admin' }; // Пример пользователя.
return true;
}
return false;
},
logout() {
this.isAuthenticated = false;
this.user = null;
},
getUser() {
return this.user;
}
};
Этот код эмулирует очень простую логику аутентификации. А теперь создадим защищённый маршрут.
Реализация защищённого маршрута
Создадим компонент ProtectedRoute, который будет проверять, авторизован ли пользователь:
// src/routes/ProtectedRoute.tsx
import React from 'react';
import { Navigate } from 'react-router-dom';
import { AuthService } from '../auth/AuthService';
interface ProtectedRouteProps {
children: JSX.Element;
redirectPath?: string;
}
export const ProtectedRoute: React.FC<ProtectedRouteProps> = ({
children,
redirectPath = '/login',
}) => {
if (!AuthService.isAuthenticated) {
return <Navigate to={redirectPath} />;
}
return children;
};
Объяснение кода:
ProtectedRoutePropsописывает пропсы компонента:children: компонент, который нужно отобразить, если пользователь авторизован.redirectPath: путь перенаправления для неавторизованных пользователей (по умолчанию/login).
Если пользователь не авторизован, мы перенаправляем его на страницу логина с помощью компонента
Navigate.
Типизация данных о пользователе
Давайте сделаем наш проект ещё более типизированным, добавив интерфейсы для описания данных пользователя.
// src/auth/AuthService.ts
export interface User {
name: string;
role: 'admin' | 'user';
}
// Обновим AuthService:
export const AuthService = {
isAuthenticated: false,
user: null as User | null,
getUser(): User | null {
return this.user;
},
// ...остальные методы без изменений.
};
Теперь мы гарантируем, что где бы ни использовались данные пользователя, TypeScript проверит их структуру. Например, при типизации компонентов.
Пример использования защищённого маршрута
Добавим маршруты и защитим доступ к одной из страниц:
// src/App.tsx
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import { ProtectedRoute } from './routes/ProtectedRoute';
import { AuthService } from './auth/AuthService';
// Страницы
const Home = () => <h1>Добро пожаловать на главную!</h1>;
const Login = () => {
const handleLogin = () => {
AuthService.login('123secureToken');
window.location.href = '/dashboard'; // Перенаправление после входа.
};
return (
<div>
<h1>Вход</h1>
<button onClick={handleLogin}>Авторизоваться</button>
</div>
);
};
const Dashboard = () => <h1>Личный кабинет</h1>;
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/login" element={<Login />} />
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
</Routes>
</Router>
);
}
export default App;
Что здесь происходит:
Мы создали три страницы:
/: Главная страница./login: Страница входа./dashboard: Личный кабинет. Доступна только авторизованным пользователям.
Для маршрута
/dashboardиспользуется компонентProtectedRoute. Он проверяет, авторизован ли пользователь.
Дополнительная типизация
Мы можем типизировать компонент Dashboard, чтобы он отображал данные текущего пользователя:
// src/pages/Dashboard.tsx
import React from 'react';
import { AuthService } from '../auth/AuthService';
const Dashboard: React.FC = () => {
const user = AuthService.getUser();
return (
<div>
<h1>Личный кабинет</h1>
{user && (
<p>
Добро пожаловать, <strong>{user.name}</strong>! Ваша роль: {user.role}.
</p>
)}
</div>
);
};
export default Dashboard;
Теперь страница личного кабинета отображает данные текущего пользователя.
Особенности и ошибки
Стоит помнить, что типизация защищённых маршрутов напрямую зависит от структуры данных аутентификации. Если структура изменится, TypeScript сразу сообщит о несоответствиях, что поможет предотвратить баги ещё на этапе разработки.
Частая ошибка — забыть передать children в ProtectedRoute. Ещё одна типичная проблема: хранение токенов в localStorage вместо httpOnly куки, что делает приложение уязвимым к XSS-атакам. Мы рассмотрим эти аспекты в последующих лекциях.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ