30 нояб. 2025 г.·4 мин

Маршрутные охранники Vue 3 для доступа по ролям: практические шаблоны

Маршрутные охранники Vue 3 для доступа по ролям: практические паттерны — meta-правила маршрутов, безопасные редиректы, дружелюбные 401/403 fallback'ы и предотвращение утечек данных.

Маршрутные охранники Vue 3 для доступа по ролям: практические шаблоны

Что на самом деле решают охранники маршрутов (и чего они не делают)

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

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

Охранники полезны, когда приложение должно вести себя последовательно и блокировать страницы, которые не следует показывать: админ-панели, внутренние инструменты или порталы с доступом по ролям.

Охранники помогают вам:

  • Блокировать страницу до её рендера
  • Перенаправлять на вход или безопасный маршрут по умолчанию
  • Показать понятную страницу 401/403 вместо сломанного вида
  • Избежать зацикливания навигации

Что охранники не сделают сами по себе — это защитят данные. Если API возвращает чувствительные данные в браузер, пользователь всё ещё может вызвать этот endpoint напрямую (или посмотреть ответы в dev tools), даже если страница заблокирована. Реальная авторизация должна происходить на сервере тоже.

Хорошая цель — покрыть обе стороны: блокировать страницы и блокировать данные. Если сотрудник поддержки откроет маршрут, доступный только администраторам, охранник должен остановить навигацию и показать «Access denied». Отдельно ваш бэкенд должен отклонять админские API-вызовы, чтобы ограниченные данные никогда не возвращались.

Выберите простую модель ролей и прав

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

Практическое разделение:

  • Роли описывают, кто человек в приложении.
  • Права (permissions) описывают, что он может делать.

Для большинства внутренних инструментов три роли решают многое:

  • admin: управляет пользователями и настройками, видит все данные
  • support: работает с записями клиентов и ответами, но не с системными настройками
  • viewer: доступ только для чтения к одобренным экранам

Решите заранее, откуда берутся роли. Токен-утверждения (JWT) быстры для охранников, но могут устаревать до обновления. Получение профиля пользователя при старте приложения всегда актуально, но ваши охранники должны ждать завершения этого запроса.

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

Определите правила доступа в meta маршрута

Самый чистый способ выразить доступ — объявить его в самом маршруте. Vue Router позволяет прикрепить объект meta к каждой записи маршрута, чтобы охранники могли прочитать его позже. Это держит правила рядом со страницами, которые они защищают.

Выберите простой формат meta и придерживайтесь его по всему приложению.

const routes = [
  {
    path: "/admin",
    component: () => import("@/pages/AdminLayout.vue"),
    meta: { requiresAuth: true, roles: ["admin"] },
    children: [
      {
        path: "users",
        component: () => import("@/pages/AdminUsers.vue"),
        // inherits requiresAuth + roles from parent
      },
      {
        path: "audit",
        component: () => import("@/pages/AdminAudit.vue"),
        meta: { permissions: ["audit:read"] },
      },
    ],
  },
  {
    path: "/tickets",
    component: () => import("@/pages/Tickets.vue"),
    meta: { requiresAuth: true, permissions: ["tickets:read"], readOnly: true },
  },
]

Для вложенных маршрутов решите, как объединять правила. В большинстве приложений дети должны наследовать требования родителя. В охраннике проверяйте все совпадающие записи маршрута (а не только to.meta), чтобы требования родителя не пропускались.

Одна деталь, которая экономит время позже: различайте «может смотреть» и «может редактировать». Маршрут может быть видим и для support, и для admin, но правки нужно отключить для support. Флаг readOnly: true в meta может управлять поведением UI (отключать действия, скрывать опасные кнопки), не маскируя это под безопасность.

Подготовьте состояние аутентификации, чтобы охранники работали надёжно

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

Обработайте auth как маленький конечный автомат и сделайте его единственным источником прав. Вам нужны три чётких состояния:

  • unknown: приложение только что запустилось, сессия ещё не проверена
  • logged out: проверка сессии завершилась, пользователя нет
  • logged in: пользователь загружен, роли/права доступны

Правило: никогда не читать роли, когда auth === unknown. Иначе вы получите вспышки защищённых экранов или неожиданные редиректы в логин.

Решите стратегию обновления сессии

Выберите одну стратегию обновления и держите её предсказуемой (например: читать токен, вызвать endpoint «who am I», установить пользователя).

Стабильный шаблон выглядит так:

  • При загрузке приложения установить auth в unknown и запустить одиночный запрос обновления
  • Разрешать охранникам только после завершения обновления (или таймаута)
  • Кэшировать пользователя в памяти, не в meta маршрута
  • При ошибке устанавливать auth в logged out
  • Экспортировать промис ready (или аналог), которого охранники могут ожидать

Когда это настроено, логика охранника остаётся простой: дождаться, пока auth будет готов, затем решать доступ.

Шаг за шагом: реализация авторизации на уровне маршрута

Create friendly fallback pages
Build 401, 403, and 404 pages fast with AppMaster UI builders.
Build UI

Чистый подход — держать большинство правил в одном глобальном охраннике и использовать per-route guards только когда маршрут действительно требует особой логики.

1) Добавьте глобальный beforeEach охранник

// router/index.js
router.beforeEach(async (to) => {
  const auth = useAuthStore()

  // Step 2: wait for auth initialization when needed
  if (!auth.ready) await auth.init()

  // Step 3: check authentication, then roles/permissions
  if (to.meta.requiresAuth && !auth.isAuthenticated) {
    return { name: 'login', query: { redirect: to.fullPath } }
  }

  const roles = to.meta.roles
  if (roles && roles.length > 0 && !roles.includes(auth.userRole)) {
    return { name: 'forbidden' } // 403
  }

  // Step 4: allow navigation
  return true
})

Это покрывает большинство случаев без разброса проверок по компонентам.

Когда beforeEnter лучше подходит

Используйте beforeEnter, когда правило действительно специфично для маршрута, например «только владелец тикета может открыть эту страницу» и это зависит от to.params.id. Держите логику короткой и используйте тот же стор аутентификации, чтобы поведение было согласованным.

Безопасные перенаправления без уязвимостей

Build a role-based portal
Create a Vue3 customer portal with auth and permissions in AppMaster.
Start Building

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

Распространённый паттерн: когда пользователь не залогинен, отправлять его на login и включать returnTo в query. После входа читать этот параметр и переходить туда. Риск — open redirect (перенаправление вне приложения) и зацикливание.

Держите поведение простым:

  • Невошедшие пользователи идут на Login с returnTo, равным текущему пути.
  • Вошедшие, но неавторизованные пользователи идут на отдельную страницу Forbidden (не на Login).
  • Разрешайте returnTo только внутренние значения, которые вы признаёте.
  • Добавьте одну проверку на цикл, чтобы никогда не перенаправлять в одно и то же место.
const allowedReturnTo = (to) => {
  if (!to || typeof to !== 'string') return null
  if (!to.startsWith('/')) return null
  // optional: only allow known prefixes
  if (!['/app', '/admin', '/tickets'].some(p => to.startsWith(p))) return null
  return to
}

router.beforeEach((to) => {
  if (!auth.isReady) return false

  if (!auth.isLoggedIn && to.name !== 'Login') {
    return { name: 'Login', query: { returnTo: to.fullPath } }
  }

  if (auth.isLoggedIn && !canAccess(to, auth.user) && to.name !== 'Forbidden') {
    return { name: 'Forbidden' }
  }
})

Избегайте утечек ограниченных данных при навигации

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

Во Vue это часто случается, когда страница делает запрос в setup(), а охранник срабатывает чуть позже. Даже если пользователя перенаправят, ответ всё ещё может попасть в общий стор или мельком отобразиться.

Более безопасное правило: сначала авторизуйте, потом загружайте.

// router guard: authorize before entering the route
router.beforeEach(async (to) => {
  await auth.ready() // ensure roles are known
  const required = to.meta.requiredRole
  if (required && !auth.hasRole(required)) {
    return { name: 'forbidden' }
  }
})

Также следите за «поздними» запросами при быстрой смене навигации. Отменяйте запросы (например, с помощью AbortController) или игнорируйте поздние ответы, проверяя id запроса.

Кэширование — ещё одна ловушка. Если вы храните «последнюю загруженную запись клиента» глобально, админский ответ может позже показаться не-админу, открывшему ту же оболочку страницы. Привязывайте кэш к id пользователя и роли, очищайте чувствительные модули при выходе (или при смене ролей).

Несколько привычек предотвращают большинство утечек:

  • Не запрашивайте чувствительные данные до подтверждения авторизации.
  • Ключируйте кэш по пользователю и роли или держите его локально на странице.
  • Отменяйте или игнорируйте запросы в полёте при смене маршрута.

Дружественные fallback-страницы: 401, 403 и not found

Ship an internal tool
Create admin panels and support dashboards with controlled access in AppMaster.
Build Tool

«Нет» пути так же важны, как «Да». Хорошие страницы-перехватчики сохраняют ориентацию пользователя и уменьшают обращения в поддержку.

401: требуется вход (не аутентифицирован)

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

403: доступ запрещён (аутентифицирован, но не разрешён)

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

Надёжная страница 403 обычно имеет понятный заголовок («Access denied»), одно предложение объяснения и безопасный следующий шаг (назад на дашборд, связаться с администратором, переключить аккаунт при поддержке этой функции).

404: не найдено

Обрабатывайте 404 отдельно от 401/403. Иначе люди подумают, что у них нет прав, когда страницы просто не существует.

Частые ошибки, которые ломают контроль доступа

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

Типичные виновники:

  • Считать скрытый UI «безопасностью». Всегда проверяйте роли в роутере и на API.
  • Читать роли из устаревшего состояния после выхода/входа.
  • Перенаправлять неавторизованных пользователей на другой защищённый маршрут (мгновенный цикл).
  • Игнорировать момент «auth всё ещё загружается» при обновлении страницы.
  • Путать 401 и 403, что сбивает с толку пользователей.

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

Быстрая проверка перед релизом

Extend access control to mobile
Create native iOS and Android apps with the same role rules behind your APIs.
Build Mobile

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

  • У каждого защищённого маршрута есть явные требования в meta.
  • Охранники обрабатывают состояние загрузки auth без мигания защищённого UI.
  • Неавторизованные пользователи попадают на понятную страницу 403 (не на сбивающий с толку редирект на главную).
  • Любое перенаправление «return to» валидируется и не может создать цикл.
  • Чувствительные API вызываются только после подтверждённой авторизации.

Затем протестируйте один сценарий от конца до конца: откройте защищённый URL в новой вкладке, будучи выlogged out, войдите как обычный пользователь и убедитесь, что вы либо попадаете на целевую страницу (если доступны права), либо на аккуратную 403 с доступным следующим шагом.

Пример: доступ support vs admin в маленьком веб-приложении

Add real backend authorization
Design APIs and business rules in AppMaster so data is protected, not just routes.
Create Backend

Представьте helpdesk с двумя ролями: support и admin. Support может читать и отвечать на тикеты. Admin может это делать и дополнительно управлять биллингом и настройками компании.

  • /tickets/:id доступен для support и admin
  • /settings/billing доступен только admin

Обычный сценарий: агент поддержки открывает закладку /settings/billing из старой закладки. Охранник должен проверить meta маршрута до загрузки страницы и заблокировать навигацию. Так как пользователь залогинен, но не имеет роли, он должен попасть на безопасный fallback (403).

Два сообщения важны:

  • Login required (401): «Please sign in to continue.»
  • Access denied (403): «You do not have access to Billing Settings.»

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

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

Следующие шаги: держать правила доступа в порядке

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

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

  • Как гость: открывайте защищённые маршруты и убеждайтесь, что вы попадаете на login без частичного контента.
  • Как пользователь: откройте страницу, к которой нет доступа, и убедитесь, что вы видите чистую 403.
  • Как админ: пробуйте глубокие ссылки из адресной строки.
  • Для каждой роли: обновляйте страницу на защищённом маршруте и проверяйте стабильность результата.

Если хотите дополнительную страховку, добавьте dev-only view или вывод в консоль, где перечислены маршруты и их meta требования — так отсутствующие правила сразу будут заметны.

Если вы строите внутренние инструменты или порталы с AppMaster (appmaster.io), тот же подход применим: держите маршрутные охранники сфокусированными на навигации в Vue3 UI и применяйте права там, где живёт логика и данные на бэкенде.

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

Вопросы и ответы

Are route guards actually security, or just UX?

Маршрутные охранники управляют навигацией, а не доступом к данным. Они помогают заблокировать страницу, перенаправить и показать аккуратный 401/403, но не мешают кому-то напрямую вызывать ваш API. Всегда дублируйте проверки прав на бэкенде, чтобы конфиденциальные данные никогда не возвращались неавторизованным клиентам.

Why isn’t hiding a menu item enough for role-based access?

Потому что скрытие UI меняет только то, что человек видит, а не то, что он может запросить. Пользователь может вручную ввести URL, открыть закладку или перейти по deep link. Нужны проверки в роутере, чтобы заблокировать страницу, и проверка на сервере, чтобы заблокировать данные.

What’s a simple roles and permissions model to start with?

Начните с небольшой и понятной модели, а дополнительные права добавляйте по мере реальной необходимости. Типичный базовый набор — admin, support, viewer. Потом можно добавлять права вроде tickets:read или audit:read для конкретных действий. Разделяйте «кем вы являетесь» (роль) и «что вы можете делать» (право/permission).

How should I use Vue Router meta for access control?

Помещайте правила доступа в meta записи маршрутов, например requiresAuth, roles и permissions. Так правила будут рядом со страницами, которые они защищают, и глобальная охрана останется предсказуемой. Для вложенных маршрутов проверяйте все совпадающие записи, чтобы не пропустить требования родителя.

How do I handle nested routes so children inherit parent restrictions?

Чтение из to.matched и объединение требований всех совпадающих записей маршрута. Тогда дочерний маршрут не сможет обойти requiresAuth или roles родителя. Решите заранее правило слияния (обычно: требования родителя применяются к детям).

How do I stop redirect loops and “flashes” of protected pages on refresh?

Потому что охранник может сработать до того, как приложение узнает, кто пользователь. Обработайте аутентификацию как три состояния — unknown, logged out, logged in — и никогда не проверяйте роли, когда состояние unknown. Заставьте охранник дождаться инициализации (например, одного запроса «who am I») перед принятием решения.

When should I use a global beforeEach guard vs beforeEnter?

По умолчанию используйте глобальный beforeEach для последовательных правил вроде «требуется вход» или «требуется роль/право». beforeEnter применяйте только когда правило действительно зависит от параметров маршрута (например, «только владелец тикета может открыть эту страницу»). Оба подхода должны обращаться к одному и тому же источнику прав пользователя.

How do I do post-login redirects without creating open redirect holes?

Обращайтесь с returnTo как с ненадёжным вводом. Разрешайте только внутренние пути (например, значения, начинающиеся с / и соответствующие известным префиксам) и добавьте проверку, чтобы не перенаправлять обратно на ту же заблокированную страницу. Невошедшие пользователи идут на Login; вошедшие, но без прав — на отдельную страницу 403.

How do I avoid leaking restricted data during navigation?

Авторизуйте прежде, чем запрашивать данные. Если страница делает fetch в setup() и вы затем перенаправляете, ответ всё равно может попасть в общий стор или мельком отобразиться. Блокируйте чувствительные запросы до подтверждённой авторизации, и отменяйте или игнорируйте «поздние» ответы при смене маршрута.

What’s the correct way to use 401 vs 403 vs 404 in a Vue app?

401 — когда пользователь не вошёл в систему; 403 — когда пользователь вошёл, но не имеет прав. 404 нужно обрабатывать отдельно, чтобы люди не путали отсутствие страницы с отсутствием прав. Последовательные и понятные страницы-ошибки уменьшают путаницу и обращения в поддержку.

Легко начать
Создай что-то невероятное

Экспериментируйте с AppMaster с бесплатной подпиской.
Как только вы будете готовы, вы сможете выбрать подходящий платный план.

Попробовать AppMaster