Первое, что я делаю, получив заказ на CRM или внутреннюю панель управления, - прошу у заказчика список ролей и что каждой из них можно делать. Без этого разговора браться за rbac роли в админке бессмысленно: угадать, кому разрешено удалять заказы, а кому - только смотреть отчёты, невозможно. За последние пару лет я собрал на React десяток панелей с разграничением доступа - от простой пары «админ/менеджер» до систем с тремя десятками разрешений - и ниже разберу подход, который реально работает в проде, а не только в демо-версии.
Ролевая модель RBAC: из чего состоит система ролей и разрешений
RBAC (Role-Based Access Control) держится на трёх сущностях. Пользователь получает одну или несколько ролей. Роль - это набор разрешений (permissions). Разрешение привязано к действию над ресурсом: orders.view, orders.delete, users.manage.
На практике я развожу роли и разрешения на отдельные сущности с первого дня, даже если сейчас всего две роли - админ и менеджер. Через полгода почти всегда добавляется бухгалтер, который видит финансовые отчёты, но не может трогать заказы, или оператор колл-центра с доступом только на чтение. Переписывать компоненты, где было захардкожено role === ‘admin’, в этот момент выходит в разы дороже, чем сразу заложить permissions.
const ROLE_PERMISSIONS = {
admin: ['orders.view', 'orders.edit', 'orders.delete', 'users.manage', 'reports.view'],
manager: ['orders.view', 'orders.edit', 'reports.view'],
accountant: ['reports.view', 'reports.export'],
operator: ['orders.view'],
};
function hasPermission(role, permission) {
return ROLE_PERMISSIONS[role]?.includes(permission) ?? false;
}
Это самый простой вариант - статичная таблица на клиенте. Он подходит для 3-4 ролей без изменений на лету. Как только заказчик просит гибкую настройку прав из интерфейса, таблицу переносят на бэкенд и отдают фронту готовый список permissions вместе с профилем пользователя.
Где хранить роли - на бэкенде или в токене
Роли и права всегда считаю источником истины на сервере. Фронт может кэшировать список разрешений, но принимать решение «можно/нельзя» для реальных операций (удаление, изменение статуса заказа, экспорт данных) обязан бэкенд - иначе разграничение доступа существует только для удобства интерфейса, а не для безопасности.
Обычная схема: при логине сервер кладёт в JWT claims с ролью и коротким списком ключевых прав, а полный набор permissions фронт запрашивает отдельным эндпоинтом вроде /me/permissions и кладёт в контекст на время сессии. На Laravel-бэкендах, которые я собираю под React-панели, права почти всегда завязаны на связку ролей и разрешений через отдельные таблицы (в духе spatie/laravel-permission), а не зашиты в enum - это снимает половину вопросов, когда заказчик просит добавить новую роль без деплоя.
Отдельно продумывайте инвалидацию: если админ поменял роль пользователя, у которого открыта сессия, права должны обновиться без перелогина - через короткий TTL кэша на фронте или через сокет-событие, которое сбрасывает контекст permissions.
Бесплатный материал
🎁 Полезный скрипт в подарок
Подпишитесь на Telegram - пришлю готовый скрипт по этой теме.
Без спама. Отписка в 1 клик.
Компонент Can и хук usePermission для проверки прав в React
Вместо того чтобы раскидывать role === ‘admin’ по компонентам, я завожу один хук и один компонент-обёртку, через которые проходит вся проверка прав.
function usePermission(permission) {
const { permissions } = useAuth();
return permissions.includes(permission);
}
function Can({ permission, children, fallback = null }) {
const allowed = usePermission(permission);
return allowed ? children : fallback;
}
// использование
<Can permission="orders.delete">
<button onClick={handleDelete}>Удалить заказ</button>
</Can>
Такой подход экономит время на ревью: разработчик видит permission прямо в JSX, а не лезет проверять, какая роль что может, по всей кодовой базе. Готовые заготовки таких хуков под разные структуры прав я собираю у себя - готовые скрипты для проверки прав доступа в React обычно беру за основу и адаптирую под роли конкретного проекта вместо написания с нуля.
Пример хука useHasRole для грубой проверки
Иногда достаточно проверить не конкретное разрешение, а саму роль - например, чтобы скрыть целый раздел меню:
function useHasRole(...roles) {
const { role } = useAuth();
return roles.includes(role);
}
Но злоупотреблять этим не стоит: если проверка «по роли» встречается больше пяти раз в кодовой базе, это обычно знак, что за ней прячется конкретное разрешение, которое стоит выделить отдельно.
Защита роутов и пунктов меню по ролям
Доступ к целым разделам панели - заказам, финансам, настройкам пользователей - проверяю на уровне роутинга, а не внутри страницы, иначе пользователь на секунду видит контент до редиректа.
function PrivateRoute({ permission, children }) {
const allowed = usePermission(permission);
if (!allowed) return <Navigate to="/403" replace />;
return children;
}
<Route
path="/finance"
element={
<PrivateRoute permission="reports.view">
<FinancePage />
</PrivateRoute>
}
/>
Пункты меню фильтрую тем же usePermission на этапе построения массива навигации, до рендера сайдбара - так пользователь вообще не видит ссылку на раздел, куда ему нет входа, и не тратит время на клик в пустоту.
Матрица доступа: таблица ролей и разрешений
Перед тем как писать код, обычно свожу права в таблицу и согласовываю с заказчиком - на этом этапе всплывает половина нюансов, которые иначе вылезли бы уже после сдачи.
| Разрешение | Администратор | Менеджер | Бухгалтер | Оператор |
|---|---|---|---|---|
| Просмотр заказов | да | да | нет | да |
| Редактирование заказов | да | да | нет | нет |
| Удаление заказов | да | нет | нет | нет |
| Финансовые отчёты | да | нет | да | нет |
| Управление пользователями | да | нет | нет | нет |
Закладывать такую матрицу на старте - вопрос одного рабочего дня совместно с заказчиком. Дорабатывать её через полгода после сдачи, когда права размазаны по компонентам, обычно упирается в отдельную консультацию (от 3 000 ₽) и переписывание доброй половины экранов - потому что при проектировании с нуля закладывать permissions в архитектуру ощутимо дешевле, чем выпиливать захардкоженные роли постфактум.
Частые ошибки при внедрении RBAC в админке
За проекты с разграничением доступа регулярно вижу одни и те же грабли:
- Проверка прав только на фронте. На одном проекте до меня насчитал больше сорока мест с role === ‘admin’ по компонентам, а API при этом отдавал данные всем авторизованным пользователям без проверки - доступ ограничивала только кнопка в интерфейсе.
- Роль зашита в имя, а не в разрешения. При добавлении новой роли вроде «старший менеджер» приходится переписывать условия по всей кодовой базе вместо правки одной строки в таблице permissions.
- Права не обновляются после смены роли без релогина - пользователь час работает со старым набором доступов и путает саппорт.
- Нет отдельного состояния 403 - вместо аккуратного сообщения пользователь видит пустой экран или ошибку в консоли.
- Проверка прав дублируется в каждом компоненте вместо общего хука - из-за этого правки логики разъезжаются по десяткам файлов.
Кастомный CRM/админ-интерфейс
Админка / CRM
от 100 000 ₽
Подробнее →Частые вопросы
Чем RBAC отличается от ABAC?
В RBAC доступ определяется ролью пользователя и привязанным к ней набором разрешений - это фиксированная схема, которую легко объяснить заказчику и отрисовать таблицей. ABAC (Attribute-Based Access Control) учитывает атрибуты: отдел сотрудника, регион, время суток, конкретный объект - доступ вычисляется динамически по правилам. Для большинства админок и CRM хватает RBAC, ABAC оправдан, когда права зависят от контекста, а не только от должности.
Нужно ли хранить права в Redux или достаточно React Context?
Для большинства панелей хватает контекста: список permissions грузится один раз при логине и обновляется по событию смены роли. Redux имеет смысл подключать, если в проекте уже есть глобальный стор для другой логики - тащить его только ради ролей избыточно.
Как быстро добавить новую роль в существующую систему?
Если права изначально разведены по permissions, а не по role === ‘x’, добавление роли сводится к новой записи в таблице на бэкенде и, при необходимости, паре строк в UI управления пользователями. Если же проверки захардкожены по ролям в компонентах, придётся пройтись по всей кодовой базе - на моей практике это занимает от нескольких дней на среднем проекте.
Что делать, если бэкенд отдаёт только role, а не список permissions?
Можно держать маппинг роль → права на фронте, как в первом примере из статьи, но тогда любое изменение прав требует релиза фронтенда. Рабочий компромисс - завести такой маппинг временно, а бэкенд дорабатывать параллельно, чтобы в итоге он сам отдавал permissions и фронт оставался тонким слоем отображения, а не источником бизнес-логики доступа.