Skip to content

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

typescript
// 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

TypeFileDescription
Static routesrouter/routes/staticRoutes.tsLogin page, error pages — no authentication required
Dynamic routesRegistered at runtimeFetched 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:

typescript
// 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:

vue
<!-- 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:

typescript
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:

typescript
// 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

  1. Use v-auth for UI elements — buttons, links, dropdown items that should be hidden without permission.
  2. Use auth() for logic branches — conditional rendering in render functions, computed properties, or setup code.
  3. 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.

Released under the MIT License.