Понимание уровней доступа
Уровни доступа позволяют ограничить функционал и страницы приложения для определённых категорий пользователей. Например:
- Пользователь: может посещать страницы аккаунта, видеть товары.
- Администратор: имеет доступ к настройкам управления, модерации и панели администратора.
Такая структура особенно важна в корпоративных или крупных приложениях, где безопасность и распределение прав доступа играют ключевую роль. Представьте: вы же не хотите, чтобы обычный пользователь случайно (или неслучайно) удалил весь контент в админке!
Определение ролей пользователей
Прежде чем настраивать маршруты, нужно решить, как вы будете хранить роли пользователей. Обычно, роль передаётся в составе JWT-токена при входе пользователя. Пример полезной части JWT-токена:
{
"id": 123,
"name": "John Doe",
"role": "admin",
"iat": 1695249600,
"exp": 1695256800
}
Здесь ключ role указывает, что пользователь является администратором.
Логика маршрутов на основе ролей
Теперь давайте реализуем механизм, который будет проверять роли пользователя и предоставлять доступ только к тем маршрутам, которые соответствуют его роли. Для этого нам понадобится:
- Компонент
PrivateRouteдля обработки защищённых маршрутов. - Хранилище состояния пользователя, откуда можно получить информацию о текущем пользователе и его роли.
Создание хранилища пользователя
Используем Context API для хранения информации о роли текущего пользователя. Контекст будет предоставлять роль и состояние авторизации.
import React, { createContext, useContext, ReactNode, useState } from 'react';
// Интерфейс данных пользователя
interface User {
id: number;
name: string;
role: 'user' | 'admin';
}
// Интерфейс контекста
interface AuthContextProps {
user: User | null;
isAuthenticated: boolean;
login: (user: User) => void;
logout: () => void;
}
// Создаём контекст
const AuthContext = createContext<AuthContextProps | undefined>(undefined);
// Провайдер контекста
export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [user, setUser] = useState<User | null>(null);
const login = (userData: User) => setUser(userData);
const logout = () => setUser(null);
return (
<AuthContext.Provider value={{ user, isAuthenticated: !!user, login, logout }}>
{children}
</AuthContext.Provider>
);
};
// Хук для использования контекста
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth должен использоваться внутри AuthProvider');
}
return context;
};
Теперь, обернув приложение в AuthProvider, мы можем получить доступ к данным пользователя в любом компоненте через хук useAuth.
Реализация компонента PrivateRoute
PrivateRoute будет проверять роль пользователя перед рендерингом защищённого маршрута. Если пользователь не авторизован или его роль не соответствует необходимой, он будет перенаправлен на страницу входа или ошибки.
import React from 'react';
import { Route, Redirect, RouteProps } from 'react-router-dom';
import { useAuth } from './AuthProvider';
// Интерфейс для роли
interface PrivateRouteProps extends RouteProps {
requiredRole?: 'user' | 'admin'; // Роль, необходимая для доступа
}
const PrivateRoute: React.FC<PrivateRouteProps> = ({ children, requiredRole, ...rest }) => {
const { user, isAuthenticated } = useAuth();
return (
<Route
{...rest}
render={({ location }) => {
if (!isAuthenticated) {
// Пользователь не авторизован — редирект на страницу входа
return <Redirect to={{ pathname: '/login', state: { from: location } }} />;
}
if (requiredRole && user?.role !== requiredRole) {
// Роль не соответствует — редирект на страницу ошибки
return <Redirect to={{ pathname: '/403' }} />;
}
// Всё ОК, рендерим компонент
return children;
}}
/>
);
};
export default PrivateRoute;
Настройка маршрутов для ролей
Теперь добавим маршруты в наше приложение. Пример включает защищённые маршруты с различными уровнями доступа:
import React from 'react';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import PrivateRoute from './PrivateRoute';
import { AuthProvider } from './AuthProvider';
import LoginPage from './pages/LoginPage';
import UserDashboard from './pages/UserDashboard';
import AdminPanel from './pages/AdminPanel';
import ForbiddenPage from './pages/ForbiddenPage';
const App: React.FC = () => {
return (
<AuthProvider>
<Router>
<Switch>
<Route path="/login" component={LoginPage} />
<PrivateRoute path="/dashboard" requiredRole="user">
<UserDashboard />
</PrivateRoute>
<PrivateRoute path="/admin" requiredRole="admin">
<AdminPanel />
</PrivateRoute>
<Route path="/403" component={ForbiddenPage} />
</Switch>
</Router>
</AuthProvider>
);
};
export default App;
Обработка ошибок и редиректов
Если пользователь без прав доступа попытается перейти на страницу, например /admin, он увидит страницу ошибки 403 Forbidden. Это улучшает пользовательский опыт, так как позволяет сразу понять, почему доступ закрыт. Вот как может выглядеть компонент ForbiddenPage:
import React from 'react';
import { Link } from 'react-router-dom';
const ForbiddenPage: React.FC = () => {
return (
<div>
<h1>403 — Доступ запрещён</h1>
<p>У вас нет прав для просмотра этой страницы. Пожалуйста, <Link to="/login">войдите</Link> под соответствующим аккаунтом.</p>
</div>
);
};
export default ForbiddenPage;
Полезные советы и типовые ошибки
Работая с уровнями доступа, помните следующее:
Не доверяйте клиенту. Проверка ролей должна происходить на сервере. Даже если клиент не позволит пользователю открыть админскую страницу, он всё равно может отправить запрос на сервер. Убедитесь, что роли проверяются и на серверной стороне.
Обновление токена. Когда пользователь переходит в новое состояние или роль обновляется, не забудьте пересоздать токен. Например, после повышения пользователя с "user" до "admin" ему нужно будет переавторизоваться.
Типизация данных. Убедитесь, что роли пользователей типизированы корректно с TypeScript, так как это упростит отладку и предотвращение ошибок.
Итак, теперь у нас есть простая, но мощная система маршрутизации с уровнями доступа. Вы можете использовать её как основу для создания сложных приложений с разными ролями пользователей.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ