Permission System
Naive Admin Max provides a complete, two-tier permission system: route-level access control and button-level granular permissions.
Route-Level Permissions
Route-level access control is enforced through Vue Router's navigation guards. Unauthenticated users attempting to access protected routes are automatically redirected to the login page.
How It Works
// router/index.ts — simplified guard logic
router.beforeEach(async (to) => {
const token = getToken()
// Logged-in users visiting /login are redirected home
if (token && to.path === '/login') return { path: '/' }
// Unmatched routes: redirect to login if not authenticated, or 404 if authenticated
if (to.matched.length === 0 || !hasNecessaryRoute(to)) {
if (!token) return { path: '/login', query: { redirect: to.fullPath } }
return { path: '/404' }
}
return true
})Static vs Dynamic Routes
| Type | File | Description |
|---|---|---|
| Static routes | router/routes/staticRoutes.ts | Login page, error pages — no authentication required |
| Dynamic routes | Registered at runtime | Fetched from backend after login, mounted as children of the Layout component |
Dynamic routes are converted from the menu data returned by the backend. The component field in each menu item maps to a file path under src/views/, resolved lazily via import.meta.glob:
// Dynamic component loading
const modules = import.meta.glob('@/views/**/*.vue')
const component = modules[`/src/views/${menuItem.component}.vue`]Button-Level Permissions
Button-level permissions allow you to show or hide specific UI elements (buttons, menu items, actions) based on the user's assigned operation codes.
The v-auth Directive
The v-auth directive hides elements when the user lacks the required permission. It supports multiple syntax forms:
<!-- Simple action code (checks against current route's actionList) -->
<n-button v-auth="'delete'">Delete</n-button>
<!-- Specify a route using colon syntax -->
<n-button v-auth="'UserList:delete'">Delete</n-button>
<!-- Multiple actions — any match passes -->
<n-button v-auth="['delete', 'UserList:delete']">Delete</n-button>
<!-- Object syntax with explicit route -->
<n-button v-auth="{ action: 'delete', route: 'UserList' }">Delete</n-button>
<!-- Object syntax with multiple routes (any match passes) -->
<n-button v-auth="{ action: 'delete', route: ['UserList', 'UserList1'] }">Delete</n-button>TIP
The directive uses display: none rather than v-if, so the element remains in the DOM. This allows permissions to update reactively when routes change without re-rendering.
The auth() Utility Function
For programmatic permission checks (e.g., in render functions or conditional logic), use the auth() function:
import { auth } from '@/utils/auth'
// Check single action
if (auth('delete')) {
// user has delete permission
}
// Check multiple actions (any match passes)
if (auth(['delete', 'export'])) {
// user has at least one of these permissions
}How Permissions Are Loaded
After login, the backend returns an actionList array in each route's meta:
// Route meta from backend menu data
{
path: '/system/user',
meta: {
title: 'User Management',
actionList: ['create', 'update', 'delete', 'export', 'resetPwd']
}
}The v-auth directive and auth() function both check against this actionList to determine visibility.
Permission Best Practices
- Use
v-authfor UI elements — buttons, links, dropdown items that should be hidden without permission. - Use
auth()for logic branches — conditional rendering in render functions, computed properties, or setup code. - Always validate on the server — front-end permissions are for UX only; your backend must enforce access control independently.
DANGER
Front-end permission checks are a UX enhancement, not a security boundary. Always enforce authorization on the server side.