Skip to content

Vue.js Integration

Fire Shield provides first-class support for Vue.js 3 with composables, directives, and router guards.

Installation

bash
npm install @fire-shield/vue@3.1.1 @fire-shield/core@3.1.1

Setup

Basic Setup

typescript
// main.ts
import { createApp } from 'vue'
import { createVueRouterRBAC } from '@fire-shield/vue'
import { RBAC } from '@fire-shield/core'
import router from './router'
import App from './App.vue'

// Initialize RBAC
const rbac = new RBAC()
rbac.createRole('admin', ['posts:*', 'users:*'])
rbac.createRole('editor', ['posts:read', 'posts:write'])
rbac.createRole('viewer', ['posts:read'])

// Create user ref
const currentUser = ref({ id: '1', roles: ['editor'] })

// Create Vue RBAC plugin
const { install: installRBAC } = createVueRouterRBAC(router, {
  rbac,
  getUser: () => currentUser.value,
  onUnauthorized: (to) => {
    router.push('/unauthorized')
  }
})

// Create and setup app
const app = createApp(App)
app.use(router)
app.use(installRBAC)
app.mount('#app')

Directives

v-can

Show elements only if user has permission:

vue
<template>
  <!-- Button visible only if user can write posts -->
  <button v-can="'posts:write'">Create Post</button>

  <!-- Section visible only if user can manage users -->
  <section v-can="'users:manage'">
    <h2>User Management</h2>
    <!-- ... -->
  </section>
</template>

v-cannot

Hide elements if user has permission (inverse of v-can):

vue
<template>
  <!-- Show upgrade prompt if user cannot access premium features -->
  <div v-cannot="'premium:access'">
    <p>Upgrade to access premium features</p>
    <button>Upgrade Now</button>
  </div>
</template>

v-permission

Alias for v-can directive:

vue
<template>
  <button v-permission="'posts:delete'">Delete Post</button>
</template>

v-role

Show elements only if user has specific role:

vue
<template>
  <!-- Admin-only panel -->
  <div v-role="'admin'">
    <h2>Admin Panel</h2>
    <!-- ... -->
  </div>

  <!-- Editor-only tools -->
  <div v-role="'editor'">
    <h3>Editor Tools</h3>
    <!-- ... -->
  </div>
</template>

Composables

Composables

Access RBAC functionality in your components using the dedicated composables:

vue
<script setup lang="ts">
import { useCan, useRole, useUser, useRBAC } from '@fire-shield/vue'

const canDelete = useCan('posts:delete')
const isAdmin = useRole('admin')
const user = useUser()
const rbac = useRBAC() // Returns RBAC instance directly

const handleAction = () => {
  if (canDelete.value) {
    // Perform delete action
  } else {
    // Show error message
  }
}
</script>

<template>
  <div>
    <p>Current user: {{ user?.id }}</p>

    <!-- Conditional rendering -->
    <button v-if="useCan('posts:write').value" @click="createPost">
      Create Post
    </button>

    <button v-if="isAdmin.value" @click="openAdminPanel">
      Admin Panel
    </button>
  </div>
</template>

API

typescript
// Returns a ComputedRef<boolean> - reactive permission check
useCan(permission: string): ComputedRef<boolean>

// Returns a ComputedRef<boolean> - reactive role check
useRole(role: string): ComputedRef<boolean>

// Returns Ref<RBACUser | null> - current user
useUser(): Ref<RBACUser | null>

// Returns the RBAC instance directly
useRBAC(): RBAC

// Returns ComputedRef<AuthorizationResult>
useAuthorize(permission: string): ComputedRef<AuthorizationResult>

// Returns ComputedRef<boolean> - checks all permissions
useAllPermissions(...permissions: string[]): ComputedRef<boolean>

// Returns ComputedRef<boolean> - checks any permission
useAnyPermission(...permissions: string[]): ComputedRef<boolean>

// Returns a function to explicitly deny a permission for the current user
useDenyPermission(): (permission: string) => void

// Returns a function to remove a previously denied permission
useAllowPermission(): (permission: string) => void

// Returns ComputedRef<string[]> - denied permissions for the current user
useDeniedPermissions(): ComputedRef<string[]>

// Returns ComputedRef<boolean> - whether a specific permission is explicitly denied
useIsDenied(permission: string): ComputedRef<boolean>

Components

Can Component

Conditionally render content based on permissions:

vue
<script setup>
import { Can } from '@fire-shield/vue'
</script>

<template>
  <Can permission="posts:write">
    <button>Create Post</button>
  </Can>

  <Can permission="posts:delete">
    <template #fallback>
      <p>You don't have permission to delete posts</p>
    </template>
    <button>Delete Post</button>
  </Can>
</template>

Cannot Component

Inverse of Can component:

vue
<script setup>
import { Cannot } from '@fire-shield/vue'
</script>

<template>
  <Cannot permission="premium:access">
    <div class="upgrade-banner">
      <p>Upgrade to unlock premium features</p>
      <button>Upgrade Now</button>
    </div>
  </Cannot>
</template>

ProtectedRoute Component

Protect entire routes:

vue
<script setup>
import { ProtectedRoute } from '@fire-shield/vue'
import AdminDashboard from './AdminDashboard.vue'
</script>

<template>
  <ProtectedRoute permission="admin:access">
    <template #fallback>
      <div>Access Denied</div>
    </template>
    <AdminDashboard />
  </ProtectedRoute>
</template>

RequirePermission Component

Require specific permission to render:

vue
<script setup>
import { RequirePermission } from '@fire-shield/vue'
</script>

<template>
  <RequirePermission permission="settings:write">
    <form @submit="saveSettings">
      <!-- Settings form -->
    </form>
  </RequirePermission>
</template>

Router Guards

Route Meta Configuration

Protect routes using meta fields:

typescript
// router/index.ts
import { createRouter } from 'vue-router'

const router = createRouter({
  routes: [
    {
      path: '/posts',
      component: PostsPage,
      meta: { permission: 'posts:read' }
    },
    {
      path: '/admin',
      component: AdminPage,
      meta: { role: 'admin' }
    },
    {
      path: '/settings',
      component: SettingsPage,
      meta: { permission: 'settings:write' }
    }
  ]
})

Enable Global Guards

typescript
const { install } = createVueRouterRBAC(router, {
  rbac,
  getUser: () => currentUser.value,
  enableGuards: true, // Enable automatic route guards
  onUnauthorized: (to, from) => {
    // Redirect to login or show error
    return '/unauthorized'
  }
})

Manual Guard Usage

For custom logic alongside RBAC guards, pass a custom onUnauthorized callback:

typescript
const { install } = createVueRouterRBAC(router, {
  rbac,
  getUser: () => currentUser.value,
  enableGuards: true,
  onUnauthorized: (to, result) => {
    if (to.path === '/special') {
      // Special handling for specific routes
      router.push('/special-unauthorized')
      return
    }
    router.push('/unauthorized')
  }
})

Reactive Permission Updates

User permissions update automatically when user changes:

vue
<script setup>
import { useUser, useCan } from '@fire-shield/vue'

const user = useUser()
const canWrite = useCan('posts:write')

// To update user, update the ref provided via createVueRouterRBAC's getUser
// The plugin re-reads the user reactively on each permission check
</script>

<template>
  <div>
    <p>Current user: {{ user?.id }}</p>

    <!-- This updates automatically when user changes -->
    <button v-can="'posts:write'">Create Post</button>
  </div>
</template>

TypeScript Support

Full TypeScript support with type inference:

typescript
import { useCan, useRole } from '@fire-shield/vue'
import type { RBACUser } from '@fire-shield/core'

// Type-safe user
const user: RBACUser = {
  id: 'user-123',
  roles: ['editor']
}

// Type-safe composables
const canWrite = useCan('posts:write')   // ComputedRef<boolean>
const isEditor = useRole('editor')       // ComputedRef<boolean>

canWrite.value   // true/false
isEditor.value   // true/false

Best Practices

1. Use Directives for Simple Cases

vue
<!-- ✅ Good: Simple, declarative -->
<button v-can="'posts:delete'">Delete</button>

<!-- ❌ Avoid: Unnecessarily complex -->
<button v-if="can('posts:delete')">Delete</button>

2. Use Composables for Logic

vue
<script setup>
import { useCan } from '@fire-shield/vue'

const canDelete = useCan('posts:delete')

const handleDelete = () => {
  if (!canDelete.value) {
    showError('No permission')
    return
  }
  deletePost()
}
</script>

3. Use Components for Complex Conditions

vue
<Can permission="posts:write">
  <template #fallback>
    <UpgradePrompt />
  </template>
  <PostEditor />
</Can>

4. Protect Routes at Router Level

typescript
// ✅ Good: Centralized route protection
{
  path: '/admin',
  meta: { permission: 'admin:access' },
  component: AdminPage
}

// ❌ Avoid: Component-level protection for routes
<template>
  <ProtectedRoute permission="admin:access">
    <AdminPage />
  </ProtectedRoute>
</template>

Examples

Check out the Vue example app for a complete working example.

Next Steps