Разработка · 6 мин чтения

RBAC роли в админке на React: как внедрить с нуля

Первое, что я делаю, получив заказ на 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 и фронт оставался тонким слоем отображения, а не источником бизнес-логики доступа.

Есть задача?

Обсудим в мессенджере

Расскажите, что нужно сделать — отвечу в течение 4 часов в рабочее время. Первая консультация бесплатно.

Самозанятый Калинкин Н. А. · работаю с физлицами и юрлицами

Продолжая пользование настоящим сайтом Вы выражаете своё согласие на обработку Ваших персональных данных (файлов куки) с использованием Yandex.Metrika.
Понятно