Role-Based Access Control (RBAC) in Vue 3: A Complete Guide

Krishna AkbariKrishna Akbari
5 min read

If you're building a Vue 3 app and wondering how to manage different user roles — like admin, editor, and viewer — you're in the right place. In this in-depth guide, we'll explore how to implement Role-Based Access Control (RBAC) using Vue Router and Vue 3’s modern capabilities.

This guide will help you:

  • ✅ Define roles in your routing system

  • ✅ Guard routes dynamically with Vue Router

  • ✅ Show/hide UI components based on user roles

  • ✅ Build a role-aware sidebar menu

  • ✅ Avoid common pitfalls and follow best practices

Let's dive in!


🔐 What is Role-Based Access Control (RBAC)?

Role-Based Access Control (RBAC) is a way to restrict what users can see or do based on their assigned role. Think of it as defining a set of permissions for different types of users:

  • Admin – full access, including user management and settings

  • Editor – can create and edit content

  • Viewer – can only read content

Instead of spreading permission logic throughout your app, RBAC helps you centralize it, making your code easier to manage and secure.

RBAC is ideal for:

  • Admin dashboards

  • Multi-user SaaS applications

  • Portals with role-specific navigation or features

🎯 Benefits of RBAC

  • Improves security by limiting access

  • Keeps templates and components clean

  • Easier to debug and maintain

  • Works great with modular architecture


🚀 Setting Up the Project

Create a fresh Vue 3 project:

npm create vue@latest vue-rbac-app
cd vue-rbac-app
npm install
npm install vue-router@4

Remove the boilerplate components and prepare a clean folder structure like this:

src/
  views/
  router/
  composables/
  components/

📍 Setting Up Vue Router with Role Metadata

Inside router/index.js, define your routes with role metadata in the meta field:

import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import Admin from '../views/Admin.vue'
import Editor from '../views/Editor.vue'
import NotAuthorized from '../views/NotAuthorized.vue'

const routes = [
  { path: '/', name: 'Home', component: Home },
  {
    path: '/admin',
    name: 'Admin',
    component: Admin,
    meta: { requiresAuth: true, roles: ['admin'] }
  },
  {
    path: '/editor',
    name: 'Editor',
    component: Editor,
    meta: { requiresAuth: true, roles: ['editor', 'admin'] }
  },
  {
    path: '/unauthorized',
    name: 'Unauthorized',
    component: NotAuthorized
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router

This makes your routing config double as your access control config.


🧪 Mocking User Authentication

In real apps, you’ll use OAuth, JWT, Firebase, etc. For now, simulate authentication using a composable.

composables/useAuth.js

export function useAuth() {
  const user = {
    isAuthenticated: true,
    role: 'editor' // Try 'admin' or 'viewer' to test
  }
  return user
}

This gives us a centralized, testable mock of the current user.


⚖️ Adding Route Guards

In main.js, intercept route changes and apply your access rules:

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { useAuth } from './composables/useAuth'

const app = createApp(App)

router.beforeEach((to, from, next) => {
  const user = useAuth()

  if (to.meta.requiresAuth) {
    if (!user.isAuthenticated) return next('/')

    if (to.meta.roles && !to.meta.roles.includes(user.role)) {
      return next('/unauthorized')
    }
  }

  next()
})

app.use(router)
app.mount('#app')

These guards enforce access control globally, before each route loads.


🧱 Basic Views for Each Role

Create simple pages to test access.

views/Home.vue

<template>
  <h1>🏠 Welcome to Home Page</h1>
</template>

views/Admin.vue

<template>
  <h1>🛠️ Admin Dashboard - For Admins Only</h1>
</template>

views/Editor.vue

<template>
  <h1>✏️ Editor Page - Editors and Admins</h1>
</template>

views/NotAuthorized.vue

<template>
  <h1>🚫 Access Denied - You’re Not Allowed Here</h1>
</template>

🎯 Role-Based UI Rendering

Sometimes, you want to conditionally show components.

DeleteButton.vue

<template>
  <button v-if="role === 'admin'">Delete Post</button>
</template>

<script setup>
import { useAuth } from '../composables/useAuth'
const { role } = useAuth()
</script>

For multiple roles:

<template>
  <button v-if="['admin', 'editor'].includes(role)">Edit Post</button>
</template>

🧭 Building a Role-Aware Sidebar

Only show links the current user can access:

const sidebarItems = [
  { name: 'Home', path: '/' },
  { name: 'Admin Panel', path: '/admin', roles: ['admin'] },
  { name: 'Edit Articles', path: '/editor', roles: ['editor', 'admin'] }
]

const user = useAuth()
const visibleLinks = sidebarItems.filter(item => {
  return !item.roles || item.roles.includes(user.role)
})

Sidebar.vue

<template>
  <ul>
    <li v-for="link in visibleLinks" :key="link.path">
      <router-link :to="link.path">{{ link.name }}</router-link>
    </li>
  </ul>
</template>

🧩 Optional: <HasRole /> Wrapper Component

Avoid repeating role-checking logic by wrapping components.

components/HasRole.vue

<template>
  <slot v-if="hasRole" />
</template>

<script setup>
import { computed } from 'vue'
import { useAuth } from '../composables/useAuth'
const props = defineProps({ roles: Array })
const { role } = useAuth()
const hasRole = computed(() => props.roles.includes(role))
</script>

Usage:

<HasRole :roles="['admin']">
  <button>Delete Post</button>
</HasRole>

📦 Next Steps

To take this further:

  • Integrate Firebase/Auth0 login

  • Store auth state in Pinia or Vuex

  • Use lazy loading with route guards

  • Add layout components for role-specific views

  • Write unit tests for route access

  • Add TypeScript for stronger role validation


📌 Final Thoughts

Role-Based Access Control isn't just about security — it's about maintaining clean structure, avoiding bugs, and scaling your application easily. The earlier you adopt RBAC, the better your codebase will handle complex user flows.

Once you grasp these patterns, you can apply them to any app — Vue, React, Angular, or even full-stack projects.

Thanks for reading!

0
Subscribe to my newsletter

Read articles from Krishna Akbari directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Krishna Akbari
Krishna Akbari