Vue Router and RBAC
Routing is more than mapping a URL to a component. In a back-office application it connects authentication, menus, permissions, data loading, code splitting, and page restoration. The security boundary remains on the server; the frontend controls navigation and visibility as a user-experience layer.
Permissions as route metadata
declare module 'vue-router' {
interface RouteMeta {
requiresAuth: boolean
permissions?: string[]
title: string
}
}
const routes = [
{
path: '/activities/publish',
component: () => import('@/views/activity/PublishView.vue'),
meta: {
requiresAuth: true,
permissions: ['activity:publish'],
title: 'Publish campaign',
},
},
]Typed metadata turns a missing permission declaration into a compile-time issue. Menus, pages, and controls should consume the same permission vocabulary rather than maintain three unrelated rule sets.
Navigation guards should orchestrate
router.beforeEach(async (to) => {
const auth = useAuthStore()
if (!to.meta.requiresAuth) return true
if (!auth.initialized) await auth.restoreSession()
if (!auth.loggedIn) return { name: 'login', query: { redirect: to.fullPath } }
if (!auth.hasAny(to.meta.permissions)) return { name: 'forbidden' }
})Session restoration belongs in a store and permission evaluation belongs in a domain function. The guard only coordinates their order. This keeps navigation logic deterministic and testable.
Dynamic route lifecycle
When routes are driven by a server-side menu, use stable route names or IDs for deduplication and preserve the removal function returned by addRoute. Remove old routes before loading permissions for a new user or role.
const disposers = serverRoutes.map(route => router.addRoute(toRouteRecord(route)))
function resetDynamicRoutes() {
disposers.splice(0).forEach(dispose => dispose())
}Four permission layers
| Layer | Frontend responsibility | Final enforcement |
|---|---|---|
| Menu | Whether an entry is visible | Authorized menu or permission response |
| Route | Whether navigation is allowed | APIs must still authorize |
| Page | Whether an area and its data are shown | Server filters data for the user |
| Operation | Whether a control is visible or enabled | Write API authorizes and audits |
Role switching must clear user state, dynamic routes, cached pages, and in-flight requests before loading the new permission set. Otherwise the new session may inherit visible UI or data from the previous role.
References: Navigation Guards, Route Meta Fields, and Navigation Failures.