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

Почему это ломается в реальных продуктах
Большинство ошибок с правами сначала кажутся мелкими. Пользователь видит действие, нажимает на него и получает отказ от сервера. Ничего не утекло, но экран всё равно выглядит сломанным.
Это несоответствие встречается часто, потому что люди судят о продукте по тому, что они могут попробовать, а не по тому, что ваш API заблокирует позже. Если на странице виден «Удалить проект» или «Сменить тариф», пользователи предполагают, что смогут это сделать. Когда бэкенд отвечает «нет», они не думают, что безопасность сработала. Они думают, что приложение потратило их время впустую.
Обратная проблема так же раздражает. Некоторые команды скрывают все элементы управления, пока не убедятся наверняка, что пользователь действительно может ими воспользоваться. Звучит аккуратно, но часто оставляет людей в неведении. Они попадают на страницу с половиной отсутствующих действий и никак не понимают — у них нет доступа, нужен более дорогой план или они открыли не ту страницу.
Проверки в UI также со временем рассогласовываются. Правило на бэкенде меняется из‑за новой роли, ограничения триала или исключения для владельцев аккаунта. Фронтенд хранит старую логику, потому что никто не обновил страницу. Через месяц служба поддержки видит странную смесь проблем: кнопки, которые видны, но падают, действия, которые должны быть видимы, но скрыты, и экраны, которые ведут себя по‑разному в зависимости от того, откуда пользователь на них пришёл.
За всем этим скрывается одна ошибка: команды принимают скрытые кнопки за гарантию безопасности. Это не так. Скрытие управления может уменьшить путаницу, и это важно, но за принуждение к выполнению действия всегда отвечает бэкенд. Если сервер не проверяет действие, любой, кто может вызвать endpoint напрямую, всё равно сможет его выполнить.
Когда функционала становится много, ситуация ухудшается. Небольшая дыра на странице биллинга превращается в ещё одну на счётах, ещё одну в управлении местами и ещё одну в настройках администратора. Каждая такая дыра сама по себе кажется безобидной. Вместе они создают продукт, который кажется произвольным.
Именно поэтому управление на основе ролей быстро портится в растущих продуктах. Команды выкатывают один экран, копируют его правила на следующий экран, а затем по одному добавляют исключения. Через некоторое время никто не доверяет тому, что показывает страница, и никому не под силу объяснить, почему один пользователь видит кнопку, которую другой никогда не получает.
Что решает UI и что решает бэкенд
UI должен помогать людям понять, что они могут попытаться сделать, что произойдёт дальше и где их ждёт ограничение. Это значит показывать или скрывать действия, выключать элементы управления, когда правило очевидно, и добавлять короткие подсказки вроде «Для изменения требуется доступ администратора». Хорошие проверки в UI экономят время. Они сокращают количество кликов, которые неизбежно закончились бы отказом.
Тем не менее браузер не даёт последнее слово. Любой может изменить запрос, воспроизвести его или вызвать API вне страницы. Если сервер доверяет экрану, правило уже сломано.
Простое разделение обязанностей работает хорошо. UI решает, что показывать, включать и объяснять. Бэкенд решает, разрешён ли чтение или запись. UI старается избежать очевидных тупиков, а бэкенд в любом случае проверяет каждый запрос.
Это разделение предотвращает ложную уверенность. Скрытая кнопка — всего лишь подсказка для пользователя. Она никогда не является защитой.
Действия, которые изменяют состояние (write actions), требуют самых строгих проверок. Если кто‑то пытается удалить запись, пригласить пользователя, сменить план или экспортировать приватные данные, сервер должен каждый раз проверять права. То же касается чтения, когда данные чувствительны. Если человеку не следует видеть зарплаты, приватные заметки или настройки другой команды, API должен блокировать эти данные даже если страница их запрашивает.
У UI всё ещё есть важная работа. Он может сделать правило понятным, а не случайным. Если пользователь открывает форму, но не может сохранить изменения, скажите ему об этом до того, как он потратит десять минут на правки. Если страница показывает часть функционала, но не рискованное действие, объясните, почему действие недоступно.
Ясные ответы от обоих слоёв важны. Экран должен задавать ожидания заранее. Сервер должен возвращать очевидный результат при подтверждении или отказе. Тогда UI сможет показать полезное сообщение вместо расплывчатой ошибки.
Хорошее правило простое: пусть UI направляет поведение, а бэкенд защищает систему. Когда оба слоя выполняют свои задачи, пользователи тратят меньше времени, а приложение остаётся безопасным даже когда браузер лжёт.
Начинайте с действий, а не с названий ролей
Названия ролей красиво смотрятся на доске, но разваливаются в продукте. «Админ» или «менеджер» может означать одно на одном экране и другое на другом. UI должен задавать более простой вопрос: что этот человек может попытаться сделать здесь?
Начинайте с экрана, а не с организационной схемы. Запишите действия, к которым реальный пользователь тянется на этой странице: просмотреть страницу, создать новый элемент, изменить данные, удалить что‑то или утвердить чувствительные изменения.
Этот список звучит базово, но быстро выявляет реальные правила. Один пользователь может просматривать карточку клиента, но не править платёжные данные. Другой может редактировать черновик, но никогда его не утверждать. Утверждение, удаление и движение денег обычно требуют более строгих проверок, потому что цена ошибки выше.
Риск должен формировать UI. Низкорискованные действия могут оставаться видимыми и удобными. Более рискованные действия требуют более ясного объяснения, шага подтверждения или вовсе отсутствия элемента управления. Здесь начинается проектирование разрешений. Экран не должен обещать действия, которые пользователь не сможет завершить.
Держите одну карту действий
Разбросанные проверки по ролям стареют плохо. Если десять компонентов говорят role === "admin", экран начнёт врать, как только бизнес добавит новую роль или изменит правило. Одна кнопка обновится, другая — нет, и люди потеряют доверие.
Лучший паттерн — назвать действия один раз, затем в одном месте сопоставлять роли с этими действиями. UI проверяет имена действий, такие как invoice.view, invoice.edit или invoice.approve. Роли мапятся на эти разрешения за кулисами. Когда правила меняются, вы обновляете маппинг, а не бегаете по всему приложению.
Это также делает продуктовые обсуждения понятнее. «Саппорт может вернуть до $50, но не может закрыть аккаунт» легче обсуждать, чем «саппорт почти админ». Правила, основанные на действиях, сначала кажутся менее аккуратными, но они соответствуют реальной работе людей и оставляют меньше места для ложной уверенности.
Простой порядок разработки
Если начать с экрана, обычно придётся гадать. Болезопасный порядок начинается на сервере. Сначала опишите правило для каждого действия: просмотр, правка, удаление, приглашение, утверждение, экспорт. Храните это правило рядом с endpoint, который выполняет работу. Именно там живёт принуждение на бэкенде.
Дайте ответ страницы, чтобы он сказал UI, что текущий пользователь может делать прямо сейчас. Отправляйте данные страницы и небольшой набор разрешённых действий вместе. Например, в ответе можно указать, что пользователь может update_plan и download_invoice, но не может change_owner. Это легче доверять, чем читать широкое имя роли и надеяться, что оно означает одно и то же везде.
Короткая последовательность работает хорошо:
- Определите правило на сервере для каждого действия и протестируйте его.
- Верните данные страницы и список разрешённых действий в одном ответе.
- Рендерите кнопки, меню и пустые состояния на основе этих действий.
- Отправляйте каждый клик на сервер, даже если UI его разрешил.
- Если правило изменилось в сессии, покажите короткую ошибку и продолжайте.
Здесь фронтенд‑авторизация помогает больше всего. Она формирует экран так, чтобы люди не тратили время на клики, которые не приведут ни к чему. Она не заменяет проверку на сервере. Вкладка может быть открыта часами. Кто‑то может изменить роль, исключить пользователя из команды или заблокировать фичу после изменения плана. UI может отражать только то, что он знал при рендере.
Когда возникает несоответствие, держите сообщение простым. «У вас больше нет доступа к этому действию» достаточно. Если перезагрузка исправит экран, скажите об этом. Не сливайте внутренние имена правил в ошибку.
Этот порядок «сервер в первую очередь» также хорошо держит при усложнении продукта. Он держит правило в одном месте, позволяет UI прочитать результат и избегает ложной уверенности, которая вызывает самые страшные баги с правами.
Пример: страница биллинга команды
Страница биллинга — хороший тест для проверок в UI, потому что люди с разными обязанностями хотят разного с одного и того же экрана. Если страница думает только в терминах ролей, она быстро станет грязной. Если она думает в терминах действий, UI останется понятным, а сервер — контролирующим.
Начните с владельца рабочего пространства. Этот человек может менять тариф, обновлять платёжные данные и подтверждать апгрейды. Экран должен показывать эти элементы явно. Никаких скрытых меню и отключённых кнопок, которые заставляют гадать.
Финансовый лидер часто нуждается в меньших правах. Ему могут быть нужны счета каждый месяц, история расходов и налоговые данные, но он не должен менять сохранённую карту или переключать план. На той же странице он может скачивать счёта и просматривать списания, тогда как элементы управления планом и картой остаются вне поля зрения.
Обычный участник может только смотреть данные о расходах. Он видит текущий план, использование, дату продления и последние списания, но страницу не стоит приглашать его редактировать что‑то. Она может оставаться полезной, не притворяясь, что у него есть контроль.
Здесь проверки по действиям побеждают широкие метки ролей. Вместо того чтобы спрашивать, является ли кто‑то админом, спрашивайте, может ли он просматривать счёта, обновлять платёжные данные или менять план. UI становится проще для понимания, а API по‑прежнему проверяет каждое из этих действий при поступлении запроса.
Показывайте ограничения, не делая экран бесполезным
Заблокированный экран всё ещё может помочь человеку завершить задачу. Хорошие проверки в UI не превращают каждый ограниченный экран в тупик. Они показывают, что человек может видеть, чего он может запросить и что он не может сделать прямо сейчас.
Отключайте действие, когда пользователю важно знать, что действие существует. Затухшая кнопка «Удалить проект» сообщает участнику команды, что удаление возможно, но только для кого‑то другого. Это важно, когда люди должны понять рабочий процесс, попросить администратора или подтвердить, что продукт вообще поддерживает такое действие.
Скрывайте действие, когда его показ создаст лишь шум. Если пользователь никогда не будет управлять настройками SSO, вкладка SSO может исчезнуть из его вида. Показывать заблокированные элементы повсюду не учит их ничему — это просто делает страницу сломанной.
Простое правило помогает: отключайте, когда видимость обучает продукту. Скрывайте, когда видимость только загромождает. Показывайте короткую причину рядом с заблокированными действиями. Держите данные только для чтения видимыми, когда это безопасно.
Эта короткая причина важнее, чем многие команды ожидают. «Редактирование биллинга только для владельцев рабочего пространства» достаточно. «Требуется разрешение Finance Admin» ещё лучше, когда роли становятся более специфичными. Люди перестают гадать, и количество тикетов в поддержку падает, потому что экран отвечает на первый вопрос за них.
Доступ только для чтения часто полезнее, чем полная блокировка. Человек может не менять роли команды, но ему нужно видеть, кто имеет доступ. Он может не обновлять платёжный метод, но ему важно проверить текущий план, дату продления или статус счёта перед обращением к менеджеру. Когда вы скрываете всё это, людям приходится спрашивать базовые факты.
Пустые страницы обычно плохой знак. Если у кого‑то ограниченный доступ, дайте ему уменьшенную версию страницы. Покажите безопасные детали, блокируйте рискованные действия и предложите один понятный путь вперёд, например попросить владельца или запросить доступ. Это сохраняет полезность экрана, не притворяясь, что фронтенд даёт последнее слово.
Бэкенд по‑прежнему защищает каждое реальное правило. UI лишь помогает людям понять границы до того, как они столкнутся с ошибкой.
Ошибки, которые порождают ложную уверенность
Большинство сломанных проверок в UI терпят тихую смерть. Экран выглядит заботливым, кнопки исчезают, и все чувствуют себя в безопасности. Но браузер — не доверенная среда. Если ваше приложение принимает значение роли из localStorage, cookie или клиентского состояния за истину, пользователь может его изменить, либо оно просто устареет.
Проблема усугубляется, когда команды копируют правила доступа по многим компонентам. Одна страница проверяет canEditBilling, другая смотрит role === "admin", модалка использует свою версию от полугодичной давности. Скоро приложение начнёт противоречить само себе. Держите клиентские правила в одном месте, используйте их только для отображения, а окончательное решение пусть принимает сервер.
Ещё одна ловушка — показывать успех до подтверждения от сервера. Пользователь нажал «Удалить», строка исчезает, и тост сообщает, что всё получилось. Две секунды спустя сервер отвергает запрос, потому что доступ пропал пять минут назад. Теперь UI соврал. Для действий, связанных с деньгами, доступом или деструктивными изменениями, короткое состояние загрузки обычно лучше, чем ложная уверенность.
Старые данные о правах вызывают ту же путаницу. Представьте, что финансовый лидер забирает доступ к биллингу у менеджера во время встречи. Менеджер по‑прежнему держит открытой страницу биллинга в другой вкладке. Если приложение хранит данные о правах слишком долго, страница продолжит показывать элементы редактирования и приглашать к действию, которое уже провалится. Обновляйте данные о правах при переключении команд, смене плана, входе в чувствительные экраны или после длительной простоя.
Команды также забывают пути, которые не начинаются с видимой кнопки. Массовые действия в таблицах, импорт CSV, закладки, сочетания клавиш и потоки повторной попытки после неудачного запроса — всё это требует тех же проверок. Пользователи не следуют аккуратному пути, который придумали дизайнеры. Они вставляют ссылки в новую вкладку, переоткрывают старые страницы и запускают импорты с устаревшим доступом.
Хороший UI на основе ролей уменьшает путаницу. Он не подтверждает полномочия. Если кто‑то может попытаться выполнить действие с любого пути, бэкенд всё равно должен либо остановить, либо разрешить это там.
Быстрые проверки перед релизом
Экран может выглядеть безопасным и при этом лгать. Обычная ложь проста: кнопки нет, значит действие заблокировано. Перед выпуском тестируйте экран так, как реальный человек его сломает, а не так, как описывает счастливый путь.
Откройте одну и ту же страницу под каждой поддерживаемой ролью. Не останавливайтесь на первой загрузке. Кликайте, открывайте боковые панели, редактируйте поле и смотрите, что меняется. Многие баги прячутся в кэше состояния страницы, где одна роль видит элементы от другой сессии.
Затем намеренно обходите страницу. Если пользователь не видит «Удалить счёт», всё равно отправьте запрос на удаление. Используйте инструменты браузера, сохранённый запрос или маленький скрипт. Бэкенд должен отвергать его каждый раз. Проверки в UI снижают путаницу, но не заменяют принуждение на сервере.
Частичные обновления требуют особого внимания. Измените роль пользователя, затем обновите только панель или модал, которые обычно обновляются на месте. Если экран по‑прежнему показывает старые действия, люди получают смешанные сигналы. Ещё хуже — они начнут задачу, которую больше не смогут завершить.
Используйте кнопку «Назад» тоже. Переоткройте старую вкладку после смены роли и проверьте, будет ли страница тихо доверять устаревшим данным. SPA часто держат больше состояния, чем ожидают, и это может сделать UI видимым правильным, пока один старый вид не прорвётся.
Также прервите задачу с истёкшей сессией. Начните редактирование, дождитесь истечения сессии, затем нажмите сохранить. Приложение должно объяснить, что произошло, простыми словами и сохранить как можно больше введённых данных. «Сессия истекла. Войдите снова, чтобы сохранить изменения» лучше, чем «403 insufficient privileges».
Проговаривайте вслух каждое сообщение об отказе. Если оно звучит как лог сервера, перепишите его. Людям нужны три вещи: что они пытались сделать, почему это не получилось и что они могут сделать дальше.
Десяти минут таких проверок зачастую хватает, чтобы поймать баги, которые блестящие демо не показывают.
Дальше, когда правила разрастаются
Логика прав редко остаётся маленькой. Продукт начинается с простых ролей, затем копятся исключения. Подрядчики могут просматривать счета, но не скачивать их. Админы команды могут редактировать биллинг, но не в период продления. Саппорт может входить в аккаунт для отладки, но не менять план. Когда это распространяется по множеству страниц, разбросанные проверки в коде представления начинают рассинхронизироваться.
Проверки в UI стареют лучше, если команда рассматривает права как продуктовые правила, а не как логику отдельных страниц. Перенесите разбросанные правила в одну общую модель. Эта модель может описывать действие, ресурс, область действия и причину отказа. UI использует её, чтобы решать, что показать, отключить или объяснить. Бэкенд по‑прежнему принуждает каждую просьбу.
Эта общая модель делает две практические вещи. Она сокращает противоречия между экранами и даёт дизайнерам, инженерам и саппорту один и тот же язык. Вместо того чтобы говорить «с биллингом у админов что‑то не так», люди могут указать на конкретное правило и исправить экран или API.
После релиза ошибки отказа заслуживают внимания. Они показывают места, где экран дал пользователям ложную уверенность. Если много людей кликают «Редактировать карту», а затем получают 403, проблема не только в контроле доступа. Страница приглашала к действию, которое никогда не сработает. Короткая пометка на странице может сэкономить кучу бесполезных кликов и сердитых тикетов.
Саппорт обычно первым замечает паттерн. Спросите, какие экраны еженедельно вызывают одинаковые вопросы по правам. Биллинг, управление участниками, экспорты и деструктивные действия — типичные проблемные зоны, потому что люди предполагают, что видимая страница отражает их полный доступ. Часто это не так.
Перед большим релизом проведите короткий обзор прав. Перечислите действия, которые пользователь может попытаться сделать, сопоставьте каждое действие с реальным правилом на бэкенде, напишите сообщение об отказе до релиза и протестируйте один разрешённый путь и один заблокированный путь для каждого действия.
Если правила теперь затрагивают многие части продукта, это уже не просто фронтенд‑чистка. Это работа над потоками продукта и границами бэкенда. Часто это тот момент, когда полезен внешний обзор. Oleg Sotnikov занимается подобной архитектурой продукта и Fractional CTO‑работой через oleg.is, помогая командам упростить запутанные модели разрешений до того, как они превратятся в застывший код политики.