7 мин

Меню навигации, учитывающие права, с обязательной проверкой доступа

Меню навигации, учитывающие права, повышают понятность интерфейса, но безопасность должна обеспечиваться на сервере. Простые паттерны для ролей, политик и безопасного скрытия UI.

Меню навигации, учитывающие права, с обязательной проверкой доступа

Какую проблему реально решают меню, учитывающие права

Когда люди говорят «скройте кнопку», обычно они имеют в виду одно из двух: уменьшить загромождение для тех, кто не может пользоваться функцией, или предотвратить неправильное использование. На фронтенде реалистична только первая цель.

Меню, учитывающие права, в основном инструмент UX. Они помогают человеку открыть приложение и сразу увидеть, что он может сделать, без постоянных «Доступ запрещён» на каждом шагу. Они также снижают нагрузку на поддержку, предотвращая путаницу вроде «Где мне одобрять счета?» или «Почему эта страница выдаёт ошибку?»

Скрытие UI — не безопасность. Это понятность.

Даже любопытный коллега всё ещё может:

  • Ввести прямую ссылку на скрытую страницу, если знает URL
  • Открыть старую закладку админ‑экрана
  • Вызвать ваш API напрямую с помощью скрипта или инструмента
  • Изменять запросы из браузера и пробовать разные идентификаторы

Так что реальная задача меню с учётом прав — честное руководство. Они выравнивают интерфейс под роль и контекст пользователя и показывают, когда что‑то недоступно.

Хорошее итоговое состояние выглядит так:

  • Пользователи в основном видят действия, которые для них логичны, и редко натыкаются на неожиданные ошибки.
  • Разработчики имеют единый источник правды для правил доступа, а не разбросанные проверки.
  • Изменения продукта безопаснее, потому что новая кнопка в меню заставляет решить, какое право она требует.
  • Безопасность обеспечивается там, где это важно: бэкенд всегда отклоняет запрещённые действия, даже если UI их по ошибке показывает.

Пример: в небольшой CRM продавец должен видеть Leads и Tasks, но не User Management. Если он всё же вставит URL управления пользователями, страница должна закрыться и сервер должен блокировать любые попытки получить список пользователей или менять роли.

Видимость — это не авторизация

Видимость — то, что интерфейс решает показать. Авторизация — то, что система действительно разрешит, когда запрос дойдёт до сервера.

Меню, учитывающие права, снижают путаницу. Если кто‑то никогда не должен видеть Billing или Admin, скрытие этих пунктов делает приложение чище и уменьшает количество обращений в поддержку. Но скрытие кнопки не является замком. Люди всё ещё могут попытаться обратиться к конечной точке через devtools, старую закладку или скопированный запрос.

Практическое правило: решите, какой опыт вы хотите дать, а затем в любом случае применяйте проверку на бэкенде, независимо от поведения UI.

При решении, как показывать действие, три паттерна покрывают большинство случаев:

  • Скрыть, когда функция должна быть недоступна для обнаружения большинством ролей (например, внутренние инструменты).
  • Отключить, когда функция есть, но пользователь не может её сейчас выполнить (например, Export отключён до апгрейда плана, пока не выбран объект или пока данные загружаются).
  • Показать с пояснением, когда пользователям нужно понять, почему они не могут продолжить («Вы можете просматривать счета, но только Owners могут менять способы оплаты»).

«Можно просматривать, но нельзя редактировать» — частый случай и его стоит явно проектировать. Рассматривайте это как два права: одно на чтение, другое на изменение. В меню вы можете показывать детали клиента всем, кто может читать, но кнопку Edit customer — только тем, у кого есть запись на запись. На странице рендерьте поля только для чтения и закрывайте контролы редактирования, при этом позволяя странице загрузиться.

Самое главное — итог решает бэкенд. Даже если UI скрывает все админ‑действия, сервер всё равно должен проверять права на каждый чувствительный запрос и возвращать понятный ответ «не разрешено», когда кто‑то пытается выполнить запрещённое действие.

Выберите модель прав, которую можно поддерживать

Самый быстрый способ внедрить меню с учётом прав — начать с модели, которую команда сможет объяснить в одно предложение. Если вы не можете объяснить её просто, вы не будете поддерживать её в корректном состоянии.

Используйте роли для группировки, а не как единственный смысл. Admin и Support — полезные корзины. Но когда роли начинают множиться (Admin‑West‑Coast‑ReadOnly), UI становится лабиринтом, а бэкенд — угадайкой.

Предпочитайте права как источник истины для того, что кто‑то может сделать. Держите их маленькими и ориентированными на действие, например invoice.create или customer.export. Это масштабируется лучше, чем разрастание ролей, потому что новые фичи обычно добавляют новые действия, а не новые должности.

Затем добавьте политики (правила) для контекста. Здесь вы обрабатываете «может редактировать только свою запись» или «может утверждать счета только до 5 000$». Политики предотвращают создание десятков почти одинаковых прав, которые отличаются только условием.

Поддерживаемый слой выглядит так:

  • Роли группируют права (функция работы)
  • Права описывают действия (что)
  • Политики добавляют контекст (когда, какие записи)

Имена имеют больше значения, чем принято думать. Если в UI написано Export Customers, а в API используется download_all_clients_v2, вы в конце концов спрячете не то или заблокируете нужное. Держите имена понятными, согласованными и общими для фронта и бэкенда:

  • Используйте noun.verb (или resource.action) последовательно
  • Сопоставляйте метки UI с целью права, а не внутренними именами кода
  • Поддерживайте старые имена прав при переименовании фич

Пример: в CRM роль Sales может включать lead.create и lead.update, но политика ограничивает обновления только своими лидами. Это делает меню понятным, а бэкенд — строгим.

Пошагово: реализуем роли и права целиком

Меню, учитывающие права, приятны тем, что уменьшают загромождение и предотвращают случайные клики. Но они помогают только тогда, когда бэкенд остаётся главным. Думайте о UI как подсказке, а о сервере как о судье.

Начните с того, чтобы записать, что вы защищаете. Не страницы, а действия. Просмотр списка клиентов отличается от экспорта клиентов и удаления клиента. Это основа меню, которые не превращаются в театрик безопасности.

Практический рецепт end‑to‑end

  1. Инвентаризация действий в UI и API. Для каждой функции перечислите конкретные операции: read, create, update, delete, export, invite, change billing, run admin reports.
  2. Определите права на сервере как источник истины. Храните соответствие ролей и прав в базе (или в конфиге) и пусть каждый API‑обработчик спрашивает «имеет ли этот пользователь право X на ресурс Y?».
  3. Возвращайте небольшой payload возможностей для UI. После входа (или обновления сессии) возвращайте булевы значения вроде canEditCustomers, canDeleteCustomers, canExport или компактный список строк прав. Держите минимально необходимое.
  4. Проверяйте на каждую операцию записи и чувствительные чтения. Все мутации обязаны проверять права. Некоторые чтения тоже (например, зарплаты, журналы аудита, экспорты или любые endpoint‑ы, которые обходят обычную фильтрацию).
  5. Добавьте несколько тестов, ориентированных на роли. Выберите 2–3 ключевые роли и протестируйте самые рискованные действия. Пример: Sales может создавать сделки, но не экспортировать клиентов; Admin может приглашать пользователей.

Небольшое, но важное правило: никогда не доверяйте флагам ролей или прав, отправленным клиентом. UI может скрывать кнопки, опираясь на возможности, но API всё равно должен отклонять неавторизованные запросы.

Паттерны фронтенда, которые остаются честными

Обрабатывайте условный доступ
Моделируйте «просмотр vs редактирование» и правила владения как переиспользуемые политики.

Меню, учитывающие права, должны помогать людям находить доступные действия, а не притворяться, что они обеспечивают безопасность. Фронтенд — направляющая рельса. Бэкенд — замок.

Стройте меню из единого источника истины

Вместо того чтобы раскидывать проверки по каждой кнопке, определите навигацию из одной конфигурации, которая включает требуемое право для каждого пункта, и отрисовывайте по этой конфигурации. Это делает правила читаемыми и снижает вероятность забыть проверку в каком‑то углу UI.

Простой паттерн выглядит так:

const menu = [
  { label: "Contacts", path: "/contacts", requires: "contacts.read" },
  { label: "Export", action: "contacts.export", requires: "contacts.export" },
  { label: "Admin", path: "/admin", requires: "admin.access" },
];

const visibleMenu = menu.filter(item => userPerms.includes(item.requires));

Предпочитайте скрывать целые секции (например, Admin) вместо того, чтобы расставлять проверки по каждой ссылке на админ‑страницы. Меньше мест для ошибок.

Честные состояния: скрыто vs отключено

Скрывайте элементы, когда пользователь никогда не будет иметь к ним доступа. Отключайте элементы, когда у пользователя есть право, но не хватает контекста.

Пример: Delete contact должен быть отключён, пока контакт не выбран. То же право, просто недостаточно контекста. Когда вы отключаете, добавьте короткое «почему» рядом с контролом (tooltip, подсказка или строка): Выберите контакт для удаления.

Набор правил, который держится:

  • Скрывайте, когда у пользователя нет права.
  • Отключайте, когда у пользователя есть право, но не выбран объект, форма неверна или данные загружаются.
  • Никогда не используйте localStorage как источник правды для прав. Это только кэш.
  • Перепроверяйте права после входа, смены роли или переключения аккаунта.

Принудительная проверка на бэкенде, которую нельзя обойти

Скрытие пунктов меню помогает людям сосредоточиться, но ничего не защищает. Бэкенд должен быть финальным арбитром, потому что запросы можно воспроизвести, изменить или вызвать вне вашего UI.

Хорошее правило: каждое действие, которое меняет данные, должно иметь одну проверку авторизации в одном месте, через которую проходит каждый запрос. Это может быть middleware, обёртка обработчика или небольшой слой политик, который вы вызываете в начале каждого endpoint‑а. Выберите один подход и придерживайтесь его, иначе вы пропустите пути.

Ставьте проверку на путь запроса

Держите авторизацию отдельно от валидации входа. Сначала решите, «может ли этот пользователь это сделать?», затем валидируйте полезную нагрузку. Если валидировать первым, вы можете выдать информацию (например, о существовании ID записи) тому, кому не следует даже знать о возможности действия.

Паттерн, который масштабируется:

  • Аутентифицируйте вызывающего и постройте явную идентичность (user id, org id, роли, права).
  • Загрузите контекст ресурса, нужный для авторизации (часто достаточно owner id или org id).
  • Вызовите одну функцию политики (например: Can(user, "invoice.delete", invoice)).
  • Если отказано — остановитесь и верните нужный статус.
  • Только затем валидируйте и выполняйте бизнес‑логику.

Возвращайте понятные, согласованные ответы

Используйте коды статусов, которые помогают фронтенду и логам:

  • 401 Unauthorized — когда вызывающий не залогинен.
  • 403 Forbidden — когда залогинен, но не разрешено.

Будьте осторожны с 404 Not Found как маской. Это полезно, чтобы не раскрывать существование ресурса, но если использовать его хаотично, отладка становится мучительной. Выберите согласованное правило для каждого типа ресурса.

Убедитесь, что одна и та же авторизация выполняется, пришёл запрос с кнопки, из мобильного приложения, из скрипта или напрямую к API.

И наконец, логируйте отказанные попытки для отладки и аудита, но храните логи в безопасности. Записывайте кто, какое действие и какой тип ресурса. Избегайте чувствительных полей, полных payload‑ов или секретов.

Краевые случаи, которые решают или ломают дизайн

Большинство багов с правами проявляются, когда пользователи идут путём, которого меню не ожидало. Поэтому меню полезно, но только если вы проектируете пути, которые его обходят.

Прямые ссылки и «скрытые» экраны

Если меню скрывает Billing для роли, пользователь всё ещё может вставить сохранённый URL или открыть его из истории браузера. Обрабатывайте каждый загруз страницы как новый запрос: получите текущие права пользователя и пусть экран сам откажется загружать защищённые данные при отсутствии права. Дружелюбное сообщение «У вас нет доступа» нормально, но реальная защита — в том, что бэкенд ничего не возвращает.

Прямые вызовы API и массовые endpoint‑ы

Любой может вызвать ваш API из devtools, скрипта или другого клиента. Поэтому проверяйте права на каждом endpoint‑е, а не только на админ‑страницах. Легко пропустить массовые действия: один /items/bulk-update может нечаянно позволить не‑админу менять поля, которых он не видит в UI.

Роли могут менять в сессии. Если админ убирает право, у пользователя может остаться старый токен или закешированное меню. Используйте короткоживущие токены или серверную проверку прав и обрабатывайте 401/403 ответ, обновляя права и UI.

Общие устройства — ещё одна ловушка: закешированное состояние меню может протечь между аккаунтами. Храните видимость меню, ключуя по ID пользователя, или вообще не персистьте её.

Пять тестов, которые стоит прогнать перед релизом:

  • Открыть прямую ссылку на скрытую страницу в роли с ограничениями
  • Вызвать защищённый API, скопировав запрос из devtools
  • Изменить роль пользователя в активной сессии и повторить действия
  • Выйти, войти под другим пользователем и убедиться, что меню сброшено
  • Попробовать массовые endpoint‑ы с полями, часть из которых разрешена, а часть нет

Пример сценария: простое CRM‑меню с реальным обеспечением

Обеспечьте проверку на бэкенде
Добавляйте проверки авторизации на сервере в каждый обработчик, а не только в меню.

Представьте внутреннюю CRM с тремя ролями: Sales, Support и Admin. Все логинятся, приложение показывает левое меню, но меню — лишь удобство. Реальная безопасность — в том, что сервер разрешает.

Вот простой набор прав, который остаётся читаемым:

  • Leads: view, create, edit, delete, export
  • Tickets: view, create, edit, assign
  • Billing: view, edit
  • Users: view, manage

UI запрашивает у бэкенда действия, разрешённые текущему пользователю (часто как список строк прав), плюс базовый контекст вроде user id и команды. Меню строится из этого. Если у вас нет billing.view, вы не увидите Billing. Если у вас есть leads.export, вы увидите кнопку Export на экране Leads. Если вы можете редактировать только свои лиды, кнопка Edit всё ещё может появляться, но быть отключённой или показывать сообщение, когда лид не ваш.

Теперь важная часть: каждый endpoint действия обеспечивает те же правила.

Пример: Sales может создавать лиды и редактировать свои лиды. Support может смотреть тикеты и назначать их, но не трогать биллинг. Admin управляет пользователями и биллингом.

Когда кто‑то пытается удалить лид, бэкенд проверяет:

  1. Имеет ли пользователь leads.delete?
  2. Если правило «только свои», совпадает ли lead.owner_id == user.id?
  3. Если лид заблокирован (например, уже инвоисован), заблокировано ли удаление для всех, кроме Admin?

Даже если Support вручную вызовет endpoint удаления, он получит forbidden. Скрытое меню не защищало — решение принял бэкенд.

Частые ошибки и ловушки

Самая большая ловушка — думать, что работа закончена, когда меню выглядит правильно. Скрытие кнопок уменьшает путаницу, но не снижает риск.

Ошибки, которые встречаются чаще всего:

  • «Невидимо» становится «невозможно». Кто‑то всё ещё может вызвать API напрямую, использовать старый URL или devtools. Считайте UI удобством, а не воротами.
  • Права проверяются только в UI. Если фронтенд решает, кто может удалить запись, вы уже проиграли. Бэкенд должен быть окончательным арбитром для каждого защищённого действия.
  • Один флаг isAdmin для всего. Это удобно сначала, потом разрастается. Вскоре каждое исключение — отдельный кейс и никто не может объяснить правила доступа.
  • Доверие клиенту в вопросах роли. Никогда не принимайте role, isAdmin или permissions из браузера за истину. Выводите идентичность и права из собственной сессии или токена, затем смотрите роли и права на сервере.
  • Забывают про права на чтение. Команды фокусируются на записях (create, update, delete), но утечки данных чаще приходят от чтений. Списки клиентов, просмотр инвойсов, экспорты CSV и поиск тоже нуждаются в проверках.

Конкретный пример: вы скрыли пункт Export leads для не‑менеджеров. Если endpoint экспорта не проверяет права, любой, кто догадается или скопирует запрос, сможет скачать файл.

Бычный чек‑лист перед релизом

Держите код портируемым
Экспортируйте исходники, когда захотите полный контроль над слоем авторизации.

Перед тем как выпустить меню с учётом прав, сделайте последний проход, ориентируясь на то, что пользователи реально могут сделать, а не на то, что они видят.

Пройдитесь по приложению как каждая основная роль и попробуйте один и тот же набор действий. Делайте это через UI и напрямую, вызывая endpoint через devtools, чтобы убедиться, что сервер — источник истины.

Чек‑лист:

  • Для каждого защищённого действия подтвердите, что на сервере есть проверка авторизации в точке выполнения (а не только при построении меню).
  • Сделайте так, чтобы UI рендерил действия по данным о возможностях от сервера (например, can‑list в сессии или отдельный endpoint разрешений), а не по догадкам из имён ролей.
  • Убедитесь, что UI корректно обрабатывает forbidden‑ответы: показывает понятное сообщение, держит страницу стабильной и не оставляет пользователя в сломанном состоянии.
  • Протестируйте смену ролей и прав. Когда админ меняет доступ, это должно применяться быстро и предсказуемо (обновление токена, инвалидирование сессии или версии прав).
  • Прогоните несколько критичных тестов для действий, связанных с деньгами, экспортами, удалениями и админ‑настройками.

Один практичный способ найти дыры: выберите одну «опасную» кнопку (delete user, export CSV, change billing) и проследите её end‑to‑end. Пункт меню должен быть скрыт, когда нужно, API должен отклонять неавторизованные вызовы, а UI должен аккуратно реагировать на 403.

Следующие шаги: выпускайте безопасно, не тормозя скорость

Начните с малого. В день релиза не нужен идеальный матрикс доступа. Выберите несколько действий, которые важны (view, create, edit, delete, export, manage users), сопоставьте их с уже имеющимися ролями и двигайтесь дальше. Когда появляется новая функция, добавляйте только те новые действия, которые она вводит.

Перед тем как строить экраны, сделайте быстрый план действий, а не страниц. Пункт меню Invoices скрывает множество действий: просмотр списка, просмотр деталей, создание, возмещение, экспорт. Их перечисление заранее делает и UI, и бэкенд яснее и предотвращает ошибку, когда закрываешь всю страницу, но оставляешь уязвимый endpoint.

При рефакторинге правил доступа относитесь к нему как к рисковой операции: делайте защитные меры. Снапшоты позволяют сравнить поведение до и после. Если роль внезапно потеряла нужные права или получила лишние, откат быстрее, чем исправление продакшена при заблокированных пользователях.

Простой релиз‑рутин помогает командам двигаться быстро и без догадок:

  • Напишите список действий для фичи и назовите каждое право.
  • Сначала добавьте проверки на бэкенде, затем подключите видимость UI к тем же правам.
  • Тестируйте одну разрешённую роль и одну запрещённую роль для каждого рискованного действия.
  • Логируйте отказанные попытки (без чувствительных данных), чтобы заметить дыры.
  • Делайте снапшот перед выпуском, чтобы быстро откатиться при необходимости.

Если вы строите на платформе с чат‑интерфейсом вроде Koder.ai (koder.ai), та же структура применима: держите права и политики определёнными один раз, пусть UI читает возможности у сервера, и делайте проверки на бэкенде обязательными в каждом обработчике.

FAQ

Что реально решают меню, учитывающие права?

Меню, учитывающие права, в первую очередь решают проблему понятности, а не безопасности. Они помогают пользователям сосредоточиться на доступных им действиях, уменьшают количество бесполезных кликов и снижают количество запросов в поддержку вроде «где мне одобрять счета?».

Безопасность всё равно должна обеспечиваться на сервере, потому что любой может попытаться открыть прямой URL, старую закладку или вызвать API напрямую, независимо от того, что показывает интерфейс.

Когда мне скрывать пункт меню, а когда отключать его?

Скрывайте, когда функция должна быть фактически недоступна для роли и нет ожидаемого пути её использования.

Отключайте, когда у пользователя может быть доступ, но сейчас недостаёт контекста — например, не выбран объект, форма неверна или данные ещё загружаются. Если вы отключаете, добавьте короткое объяснение, чтобы не казалось, что элемент сломан.

Почему «скрыть кнопку» — это не безопасность?

Потому что видимость — это не авторизация. Пользователь может вставить URL, открыть закладку админ‑страницы или вызвать API вне вашего UI.

Рассматривайте интерфейс как подсказку. Рассматривайте бэкенд как окончательного арбитра для каждого защищённого запроса.

Откуда фронтенд должен знать, что пользователь может делать?

Сервер должен возвращать небольшой набор возможностей после входа в систему или обновления сессии, основанный на проверках на стороне сервера. UI рендерит меню и кнопки по этим данным.

Не доверяйте флагам наподобие isAdmin, приходящим из браузера; вычисляйте права по аутентифицированной идентичности на сервере.

Как проще всего реализовать права end‑to‑end?

Начните с инвентаря действий, а не страниц. Для каждой функции перечислите read, create, update, delete, export, invite, смена биллинга и т.д.

Затем проверяйте каждое действие в обработчике на бэкенде (или в middleware/обёртке) до выполнения работы. Свяжите меню с теми же именами прав, чтобы UI и API были согласованы.

Использовать роли или права в качестве основной модели?

Практический подход: роли — это наборы, а права — источник истины. Делайте права небольшими и ориентированными на действия (например, invoice.create) и назначайте их ролям.

Если роли начинают множиться для кодирования условий (регион, доступ по владению), вынесите эти условия в политики вместо создания десятков вариантов роли.

Как обрабатывать «может просматривать, но не редактировать» и другие условные права?

Используйте политики для контекстных правил вроде «может редактировать только свои записи» или «может утверждать счета до определённой суммы». Это позволяет держать список прав стабильным и выражать реальные ограничения.

Бэкенд должен оценивать политику на основании контекста ресурса (например, owner ID или org ID), а не предположений от UI.

Нужны ли проверки прав для endpoint‑ов чтения?

Не всегда. Но чтения, которые раскрывают чувствительные данные или обходят обычную фильтрацию, тоже нужно проверять: экспорты, журналы аудита, зарплаты, списки пользователей админки и т.п.

Хорошее правило: все записи, изменяющие данные, обязаны иметь проверку; чувствительные чтения тоже требуют контроля.

Как избежать дыр безопасности для bulk‑операций и «power» endpoint‑ов?

Для массовых endpoint‑ов риск велик: один /items/bulk-update может позволить изменить поля, которых пользователь не видит в UI.

Проверяйте права для самой массовой операции и дополнительно валидируйте, какие поля разрешено менять для данной роли, иначе можно случайно разрешить редактирование скрытых полей.

Что делать, если роли меняются в середине сессии или кеш UI устарел?

Предполагаете, что права могут поменяться в сессии. При получении 401/403 UI должен корректно обновить возможности, пересобрать меню и показать понятное сообщение.

Избегайте сохранения видимости меню таким образом, чтобы оно могло протечь между аккаунтами на общих устройствах; если кешируете, ключуйте по идентичности пользователя или не сохраняйте вовсе.

Похожие статьи