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

Когда люди говорят «скройте кнопку», обычно они имеют в виду одно из двух: уменьшить загромождение для тех, кто не может пользоваться функцией, или предотвратить неправильное использование. На фронтенде реалистична только первая цель.
Меню, учитывающие права, в основном инструмент UX. Они помогают человеку открыть приложение и сразу увидеть, что он может сделать, без постоянных «Доступ запрещён» на каждом шагу. Они также снижают нагрузку на поддержку, предотвращая путаницу вроде «Где мне одобрять счета?» или «Почему эта страница выдаёт ошибку?»
Скрытие UI — не безопасность. Это понятность.
Даже любопытный коллега всё ещё может:
Так что реальная задача меню с учётом прав — честное руководство. Они выравнивают интерфейс под роль и контекст пользователя и показывают, когда что‑то недоступно.
Хорошее итоговое состояние выглядит так:
Пример: в небольшой CRM продавец должен видеть Leads и Tasks, но не User Management. Если он всё же вставит URL управления пользователями, страница должна закрыться и сервер должен блокировать любые попытки получить список пользователей или менять роли.
Видимость — то, что интерфейс решает показать. Авторизация — то, что система действительно разрешит, когда запрос дойдёт до сервера.
Меню, учитывающие права, снижают путаницу. Если кто‑то никогда не должен видеть Billing или Admin, скрытие этих пунктов делает приложение чище и уменьшает количество обращений в поддержку. Но скрытие кнопки не является замком. Люди всё ещё могут попытаться обратиться к конечной точке через devtools, старую закладку или скопированный запрос.
Практическое правило: решите, какой опыт вы хотите дать, а затем в любом случае применяйте проверку на бэкенде, независимо от поведения UI.
При решении, как показывать действие, три паттерна покрывают большинство случаев:
«Можно просматривать, но нельзя редактировать» — частый случай и его стоит явно проектировать. Рассматривайте это как два права: одно на чтение, другое на изменение. В меню вы можете показывать детали клиента всем, кто может читать, но кнопку 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) последовательноПример: в CRM роль Sales может включать lead.create и lead.update, но политика ограничивает обновления только своими лидами. Это делает меню понятным, а бэкенд — строгим.
Меню, учитывающие права, приятны тем, что уменьшают загромождение и предотвращают случайные клики. Но они помогают только тогда, когда бэкенд остаётся главным. Думайте о UI как подсказке, а о сервере как о судье.
Начните с того, чтобы записать, что вы защищаете. Не страницы, а действия. Просмотр списка клиентов отличается от экспорта клиентов и удаления клиента. Это основа меню, которые не превращаются в театрик безопасности.
canEditCustomers, canDeleteCustomers, canExport или компактный список строк прав. Держите минимально необходимое.Небольшое, но важное правило: никогда не доверяйте флагам ролей или прав, отправленным клиентом. UI может скрывать кнопки, опираясь на возможности, но API всё равно должен отклонять неавторизованные запросы.
Меню, учитывающие права, должны помогать людям находить доступные действия, а не притворяться, что они обеспечивают безопасность. Фронтенд — направляющая рельса. Бэкенд — замок.
Вместо того чтобы раскидывать проверки по каждой кнопке, определите навигацию из одной конфигурации, которая включает требуемое право для каждого пункта, и отрисовывайте по этой конфигурации. Это делает правила читаемыми и снижает вероятность забыть проверку в каком‑то углу 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) вместо того, чтобы расставлять проверки по каждой ссылке на админ‑страницы. Меньше мест для ошибок.
Скрывайте элементы, когда пользователь никогда не будет иметь к ним доступа. Отключайте элементы, когда у пользователя есть право, но не хватает контекста.
Пример: Delete contact должен быть отключён, пока контакт не выбран. То же право, просто недостаточно контекста. Когда вы отключаете, добавьте короткое «почему» рядом с контролом (tooltip, подсказка или строка): Выберите контакт для удаления.
Набор правил, который держится:
Скрытие пунктов меню помогает людям сосредоточиться, но ничего не защищает. Бэкенд должен быть финальным арбитром, потому что запросы можно воспроизвести, изменить или вызвать вне вашего UI.
Хорошее правило: каждое действие, которое меняет данные, должно иметь одну проверку авторизации в одном месте, через которую проходит каждый запрос. Это может быть middleware, обёртка обработчика или небольшой слой политик, который вы вызываете в начале каждого endpoint‑а. Выберите один подход и придерживайтесь его, иначе вы пропустите пути.
Держите авторизацию отдельно от валидации входа. Сначала решите, «может ли этот пользователь это сделать?», затем валидируйте полезную нагрузку. Если валидировать первым, вы можете выдать информацию (например, о существовании ID записи) тому, кому не следует даже знать о возможности действия.
Паттерн, который масштабируется:
Can(user, "invoice.delete", invoice)).Используйте коды статусов, которые помогают фронтенду и логам:
401 Unauthorized — когда вызывающий не залогинен.403 Forbidden — когда залогинен, но не разрешено.Будьте осторожны с 404 Not Found как маской. Это полезно, чтобы не раскрывать существование ресурса, но если использовать его хаотично, отладка становится мучительной. Выберите согласованное правило для каждого типа ресурса.
Убедитесь, что одна и та же авторизация выполняется, пришёл запрос с кнопки, из мобильного приложения, из скрипта или напрямую к API.
И наконец, логируйте отказанные попытки для отладки и аудита, но храните логи в безопасности. Записывайте кто, какое действие и какой тип ресурса. Избегайте чувствительных полей, полных payload‑ов или секретов.
Большинство багов с правами проявляются, когда пользователи идут путём, которого меню не ожидало. Поэтому меню полезно, но только если вы проектируете пути, которые его обходят.
Если меню скрывает Billing для роли, пользователь всё ещё может вставить сохранённый URL или открыть его из истории браузера. Обрабатывайте каждый загруз страницы как новый запрос: получите текущие права пользователя и пусть экран сам откажется загружать защищённые данные при отсутствии права. Дружелюбное сообщение «У вас нет доступа» нормально, но реальная защита — в том, что бэкенд ничего не возвращает.
Любой может вызвать ваш API из devtools, скрипта или другого клиента. Поэтому проверяйте права на каждом endpoint‑е, а не только на админ‑страницах. Легко пропустить массовые действия: один /items/bulk-update может нечаянно позволить не‑админу менять поля, которых он не видит в UI.
Роли могут менять в сессии. Если админ убирает право, у пользователя может остаться старый токен или закешированное меню. Используйте короткоживущие токены или серверную проверку прав и обрабатывайте 401/403 ответ, обновляя права и UI.
Общие устройства — ещё одна ловушка: закешированное состояние меню может протечь между аккаунтами. Храните видимость меню, ключуя по ID пользователя, или вообще не персистьте её.
Пять тестов, которые стоит прогнать перед релизом:
Представьте внутреннюю CRM с тремя ролями: Sales, Support и Admin. Все логинятся, приложение показывает левое меню, но меню — лишь удобство. Реальная безопасность — в том, что сервер разрешает.
Вот простой набор прав, который остаётся читаемым:
UI запрашивает у бэкенда действия, разрешённые текущему пользователю (часто как список строк прав), плюс базовый контекст вроде user id и команды. Меню строится из этого. Если у вас нет billing.view, вы не увидите Billing. Если у вас есть leads.export, вы увидите кнопку Export на экране Leads. Если вы можете редактировать только свои лиды, кнопка Edit всё ещё может появляться, но быть отключённой или показывать сообщение, когда лид не ваш.
Теперь важная часть: каждый endpoint действия обеспечивает те же правила.
Пример: Sales может создавать лиды и редактировать свои лиды. Support может смотреть тикеты и назначать их, но не трогать биллинг. Admin управляет пользователями и биллингом.
Когда кто‑то пытается удалить лид, бэкенд проверяет:
leads.delete?lead.owner_id == user.id?Даже если Support вручную вызовет endpoint удаления, он получит forbidden. Скрытое меню не защищало — решение принял бэкенд.
Самая большая ловушка — думать, что работа закончена, когда меню выглядит правильно. Скрытие кнопок уменьшает путаницу, но не снижает риск.
Ошибки, которые встречаются чаще всего:
isAdmin для всего. Это удобно сначала, потом разрастается. Вскоре каждое исключение — отдельный кейс и никто не может объяснить правила доступа.role, isAdmin или permissions из браузера за истину. Выводите идентичность и права из собственной сессии или токена, затем смотрите роли и права на сервере.Конкретный пример: вы скрыли пункт Export leads для не‑менеджеров. Если endpoint экспорта не проверяет права, любой, кто догадается или скопирует запрос, сможет скачать файл.
Перед тем как выпустить меню с учётом прав, сделайте последний проход, ориентируясь на то, что пользователи реально могут сделать, а не на то, что они видят.
Пройдитесь по приложению как каждая основная роль и попробуйте один и тот же набор действий. Делайте это через UI и напрямую, вызывая endpoint через devtools, чтобы убедиться, что сервер — источник истины.
Чек‑лист:
Один практичный способ найти дыры: выберите одну «опасную» кнопку (delete user, export CSV, change billing) и проследите её end‑to‑end. Пункт меню должен быть скрыт, когда нужно, API должен отклонять неавторизованные вызовы, а UI должен аккуратно реагировать на 403.
Начните с малого. В день релиза не нужен идеальный матрикс доступа. Выберите несколько действий, которые важны (view, create, edit, delete, export, manage users), сопоставьте их с уже имеющимися ролями и двигайтесь дальше. Когда появляется новая функция, добавляйте только те новые действия, которые она вводит.
Перед тем как строить экраны, сделайте быстрый план действий, а не страниц. Пункт меню Invoices скрывает множество действий: просмотр списка, просмотр деталей, создание, возмещение, экспорт. Их перечисление заранее делает и UI, и бэкенд яснее и предотвращает ошибку, когда закрываешь всю страницу, но оставляешь уязвимый endpoint.
При рефакторинге правил доступа относитесь к нему как к рисковой операции: делайте защитные меры. Снапшоты позволяют сравнить поведение до и после. Если роль внезапно потеряла нужные права или получила лишние, откат быстрее, чем исправление продакшена при заблокированных пользователях.
Простой релиз‑рутин помогает командам двигаться быстро и без догадок:
Если вы строите на платформе с чат‑интерфейсом вроде Koder.ai (koder.ai), та же структура применима: держите права и политики определёнными один раз, пусть UI читает возможности у сервера, и делайте проверки на бэкенде обязательными в каждом обработчике.
Меню, учитывающие права, в первую очередь решают проблему понятности, а не безопасности. Они помогают пользователям сосредоточиться на доступных им действиях, уменьшают количество бесполезных кликов и снижают количество запросов в поддержку вроде «где мне одобрять счета?».
Безопасность всё равно должна обеспечиваться на сервере, потому что любой может попытаться открыть прямой URL, старую закладку или вызвать API напрямую, независимо от того, что показывает интерфейс.
Скрывайте, когда функция должна быть фактически недоступна для роли и нет ожидаемого пути её использования.
Отключайте, когда у пользователя может быть доступ, но сейчас недостаёт контекста — например, не выбран объект, форма неверна или данные ещё загружаются. Если вы отключаете, добавьте короткое объяснение, чтобы не казалось, что элемент сломан.
Потому что видимость — это не авторизация. Пользователь может вставить URL, открыть закладку админ‑страницы или вызвать API вне вашего UI.
Рассматривайте интерфейс как подсказку. Рассматривайте бэкенд как окончательного арбитра для каждого защищённого запроса.
Сервер должен возвращать небольшой набор возможностей после входа в систему или обновления сессии, основанный на проверках на стороне сервера. UI рендерит меню и кнопки по этим данным.
Не доверяйте флагам наподобие isAdmin, приходящим из браузера; вычисляйте права по аутентифицированной идентичности на сервере.
Начните с инвентаря действий, а не страниц. Для каждой функции перечислите read, create, update, delete, export, invite, смена биллинга и т.д.
Затем проверяйте каждое действие в обработчике на бэкенде (или в middleware/обёртке) до выполнения работы. Свяжите меню с теми же именами прав, чтобы UI и API были согласованы.
Практический подход: роли — это наборы, а права — источник истины. Делайте права небольшими и ориентированными на действия (например, invoice.create) и назначайте их ролям.
Если роли начинают множиться для кодирования условий (регион, доступ по владению), вынесите эти условия в политики вместо создания десятков вариантов роли.
Используйте политики для контекстных правил вроде «может редактировать только свои записи» или «может утверждать счета до определённой суммы». Это позволяет держать список прав стабильным и выражать реальные ограничения.
Бэкенд должен оценивать политику на основании контекста ресурса (например, owner ID или org ID), а не предположений от UI.
Не всегда. Но чтения, которые раскрывают чувствительные данные или обходят обычную фильтрацию, тоже нужно проверять: экспорты, журналы аудита, зарплаты, списки пользователей админки и т.п.
Хорошее правило: все записи, изменяющие данные, обязаны иметь проверку; чувствительные чтения тоже требуют контроля.
Для массовых endpoint‑ов риск велик: один /items/bulk-update может позволить изменить поля, которых пользователь не видит в UI.
Проверяйте права для самой массовой операции и дополнительно валидируйте, какие поля разрешено менять для данной роли, иначе можно случайно разрешить редактирование скрытых полей.
Предполагаете, что права могут поменяться в сессии. При получении 401/403 UI должен корректно обновить возможности, пересобрать меню и показать понятное сообщение.
Избегайте сохранения видимости меню таким образом, чтобы оно могло протечь между аккаунтами на общих устройствах; если кешируете, ключуйте по идентичности пользователя или не сохраняйте вовсе.