u/Confident_Act5884

▲ 14 r/vuejs

Hey There!

Authorization logic is one of those things that starts simple and ends up scattered everywhere - v-if="user.role === 'admin'" in 40 different components, no inheritance, no fallback states. I've been there too many times, so I built vue-rbac to fix it properly.

What it does

vue-rbac is a lightweight, dependency-free RBAC plugin for Vue 3 that gives you a consistent, declarative way to handle roles and permissions.

Full feature list

  • Role & permission system with inheritance chains (admin → editor → viewer)
  • 5 directives: v-rbac, v-rbac:role, v-rbac:any, v-rbac:all, v-rbac:not
  • Wildcard permissionsusers:* grants all actions on a resource
  • <RbacGuard> component with #fallback and #loading slots
  • useRBAC() composable for programmatic access & reactive state
  • 3 config modes: static, dynamic (fetch from API), and hybrid
  • Built-in storage adapters: localStorage, sessionStorage, cookies
  • TTL-based caching with manual invalidation
  • Retry with exponential backoff for failed fetches
  • Configurable log levels
  • Zero dependencies, TypeScript-first

Install

pnpm add @nangazaki/vue-rbac

Basic setup

app.use(VueRBAC, {
  config: {
    mode: CONFIG_MODE.STATIC,
    roles: {
      admin: {
        permissions: ['users:create', 'posts:create'],
        inherits: ['editor'],
      },
      editor: {
        permissions: ['posts:edit'],
        inherits: ['viewer'],
      },
      viewer: { permissions: ['posts:view'] },
    },
  },
})

Directives — keep your templates clean

<button v-rbac="'users:create'">Add User</button>

<div v-rbac:role="'admin'">Admin Panel</div>

<div v-rbac:any="['posts:edit', 'posts:create']">Editor or Admin</div>

<div v-rbac:all="['posts:edit', 'posts:publish']">Full Editor Access</div>

<div v-rbac:not="'admin'">Visible to non-admins only</div>

Wildcard permissions

Assign resource:* to grant all actions on that resource:

roles: { superadmin: { permissions: ['users:*'] } }

rbac.hasPermission('users:create') // true
rbac.hasPermission('users:delete') // true
rbac.hasPermission('posts:create') // false — different resource

RbacGuard component — with fallback & loading slots

When directives aren't enough (e.g. you need to show something on denial, or handle a loading state while roles are fetched):

<RbacGuard permission="users:create">
  <CreateUserForm />

  <template #fallback>
    <p>You don't have access to this.</p>
  </template>

  <template #loading>
    <Spinner />
  </template>
</RbacGuard>

Supports the same props as the directives: role, permission, any, all, not.

TTL cache + retry with exponential backoff

In dynamic mode, fetched roles are cached to avoid redundant API calls. You control the TTL and can invalidate manually:

app.use(VueRBAC, {
  config: {
    mode: CONFIG_MODE.DYNAMIC,
    fetchRoles: async () => (await fetch('/api/roles')).json(),
    cacheTtl: 30 * 60 * 1000,   // 30 minutes (set 0 to disable)
    retry: { attempts: 3, delay: 1000, backoff: 2 }, // 1s → 2s → 4s
  },
})

// Invalidate manually (e.g. after login/logout):
const { invalidateCache } = useRBAC()
invalidateCache()

useRBAC() composable

const { state, setUserRoles, hasRole, hasPermission, hasAnyPermission } = useRBAC()

state.isLoading      // true while roles are being fetched
state.userRoles      // reactive list of active roles

setUserRoles(['admin', 'editor'])
hasPermission('posts:create')                   // true
hasAnyPermission(['posts:edit', 'posts:view'])  // true

GitHub: https://github.com/nangazaki/vue-rbac

Docs: https://vue-rbac.nangazaki.io

I'd love your feedback on:

  • Is the API intuitive? Anything that feels awkward?
  • Use cases that aren't covered yet?
  • Performance or architecture concerns?

Thanks for checking it out!

u/Confident_Act5884 — 16 days ago