Skip to main contentArrow Right

Table of Contents

This tutorial was written by Kevin Kimani, a software developer and technical writer. Connect with him on his GitHub or X to see more of his work!


Authentication and access control are critical for any Vue.js application that protects private content or admin features. While it may sound straightforward, authentication in Vue.js can quickly become complex as apps scale and require features like session management, MFA, and role-based permissions. Building these systems from scratch often slows development and introduces security risks.

In this tutorial, you’ll learn step by step how to add authentication in Vue.js using Descope. You’ll implement secure login with email one-time passwords (OTP), set up multi-factor authentication (MFA), and configure role-based access control (RBAC) to restrict access to admin functionality. By the end, you’ll have a working Vue.js authentication system backed by Descope’s flow-based identity management—letting you focus on building features instead of handling low-level auth logic.

Prerequisites

To complete this tutorial, you need to have the following:

  • A Descope account – Sign up for free to follow along with the tutorial

  • Basic understanding of Descope flows and how to use the flow builder to configure authentication steps.

  • Node.js v18.3 or higher and npm – Installed locally to run the Vue.js application.

  • A code editor and a web browser – VS Code and Chrome are recommended, but any editor and browser will work.

Here’s a quick look at the rough architecture of the Vue.js authentication you’ll create:

Vue.js tutorial architecture diagram
Fig: Rough architecture diagram

Creating a project on Descope

The first step in setting up authentication in Vue.js is creating a new project in your Descope console.

Open your Descope console. From the project drop-down, click + Project to create a new one.

Creating a new project on Descope
Fig: Creating a new project on Descope

On the Create project modal, provide the project name (for example, descope-vuejs-auth-rbac), and click Create:

Providing the new project details
Fig: Providing the new project details

Once created, you’ll land on the project’s home page. Click Get Started to configure authentication.

Project home page
Fig: Project home page

On the Who uses your application? screen, select Consumers and click Next:

Who uses your application
Fig: Who uses your application

In your Vue.js application, you’ll use OTP via email for authentication and SMS OTP as a second factor. On the Which authentication methods do you want to use? screen, select One-time Password as the primary method, then click Next.

Choosing the primary authentication method
Fig: Choosing the primary authentication method

Skip MFA for now by clicking Go ahead without MFA. We’ll configure MFA later in the flow builder, where handle it using the imported flow,:

Skipping the MFA step
Fig: Skipping the MFA step

On the We’re now ready to show you Descope in action! page, select Next to generate your authentication flows:

Generating all flows
Fig: Generating all flows

In just a few steps, you’ve configured a working Vue.js authentication process with Descope, which you can later customize as needed.

Customize the authentication flow

After these steps, Descope generates the default flows. In the sidebar of your console, go to Flows and click the sign-up-or-in flow to open it in the visual builder.

Opening the sign-up-or-in flow in the flow builder
Fig: Opening the sign-up-or-in flow in the flow builder

Currently, this flow allows the user to log in via SMS OTP:

Current flow configuration
Fig: Current flow configuration

For this tutorial, you’ll customize it to use email OTP for sign-in and SMS OTP for MFA. To make it easier to follow along, you’ll use a prepared flow file with these settings already configured.

Download the raw flow file from GitHub by clicking the Download button:

Downloading the flow raw file
Fig: Downloading the flow raw file

Next, in the flow builder, click the Import flow / Export button, select Import flow, and upload the flow you just downloaded in the previous step:

Importing a flow
Fig: Importing a flow

The imported flow begins with a Welcome screen where the user enters their email address. Descope then sends an OTP to that email. After the user enters the correct OTP, the flow checks whether they are a new or returning user. New users are prompted to provide their full name and phone number, which are stored in the Descope data store. They then receive an SMS OTP to verify their phone number before being logged in. Returning users skip this step and simply receive an SMS OTP sent to their registered phone number. Once verified, they are logged in automatically.

Once the flow is imported, click Save.

Saving the flow
Fig: Saving the flow

At this point, you have a functional email-plus-SMS OTP authentication flow. Descope uses its default SMS connector for delivery, but you can also configure your own provider, such as AWS SNS, Twilio, or Twilio Verify if needed. You can use any of these in the Descope console to send messages.

Finally, open the Project tab from the sidebar and copy your Project ID—you’ll need it when setting up the Vue.js application in the next section.

Cloning the starter project

To make it easier to follow along, this tutorial uses a premade starter template that includes the basic structure of the Vue.js application.

Begin by cloning the project to your local machine:

git clone --single-branch -b starter-template https://github.com/kimanikevin254/descope-vuejs-auth-rbac-demo.git

Next, navigate into the project folder and install the dependencies:

cd descope-vuejs-auth-rbac-demo && npm i

The starter template is built around a simple job board concept. Users can browse available jobs, while administrators can publish or unpublish job postings. To keep things simple, the project uses json-server to mock backend functionality, with several predefined jobs stored in json-server/db.json.

To run the application locally, first start the mock server: npm run server. Then, in a separate terminal window, run the development server: npm run dev. Once both servers are running, navigate to http://localhost:5173 in your browser.

Home page
Fig: Home page

On the home page, you can click View Available Jobs to see all published jobs:

Available jobs
Fig: Available jobs

Click the Go to Admin Dashboard web page to access the admin dashboard, where you can toggle the published status of each job.

Admin dashboard
Fig: Admin dashboard

At this stage, anyone can view jobs and open the Admin Dashboard. In the next section, you’ll integrate authentication in Vue.js with Descope so that only logged-in users can see available jobs, and only those with the proper role can access the dashboard.

Adding authentication to the Vue.js application

You’ll add authentication and RBAC to the Vue.js application with Descope. This ensures that only authenticated users can view available jobs, and only those with the Admin Dashboard Access permission can open the Admin Dashboard. To get started, install the Vue SDK with the following command:

npm i --save @descope/vue-sdk

Once installed, add the Descope plugin to your application. Open src/main.js and replace the existing code with the following:

import "./assets/main.css";

import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import descope from "@descope/vue-sdk";

const app = createApp(App);

app.use(descope, {
 projectId: import.meta.env.VITE_DESCOPE_PROJECT_ID,
});

app.use(router);

app.mount("#app");

This code imports the Descope plugin and registers it in your Vue.js application. It requires the Project ID you copied earlier. To keep credentials secure, create a .env file in the project root and add:

VITE_DESCOPE_PROJECT_ID=<YOUR-DESCOPE-PROJECT-ID>

Remember to replace the placeholder with your actual project ID.

Next, you need to modify the src/views/LoginView.vue file to allow Descope to handle authentication. Open the file and replace the existing code with the following:

<script setup>
import { Descope, useSession } from "@descope/vue-sdk";
import { ref, watch } from "vue";
import { useRouter } from "vue-router";

const router = useRouter();
const { isLoading, isAuthenticated } = useSession();

// Redirect if isAuthenticated is true
watch(isAuthenticated, (newValue) => {
 console.log("New auth status", newValue);
 if (newValue === true) {
 router.replace({ name: "jobs" });
 }
});
</script>

<template>
 <div class="h-screen max-w-xl mt-4 mx-auto">
 <p v-if="isLoading">Loading...</p>
 <Descope v-if="!isAuthenticated" :flowId="'sign-up-or-in'" />
 </div>
</template>

This code uses the useSession() hook from the Descope SDK to track the user’s authentication status (isAuthenticated) and the session loading state (isLoading). While the session is loading, the template displays a loading message. If the user is not authenticated, the Descope component renders the sign-up-or-in flow you configured earlier. The watch function listens for changes to isAuthenticated, and once the user is authenticated, it redirects them to the Jobs view using Vue Router’s replace() method.

Next, set up Vue Router navigation guards to protect routes that require authentication. While there are several ways to do this, this guide uses a global beforeEach guard. In src/router/index.js file, this guide uses a global beforeEach guard. In src/router/index.js, most route objects already include a meta property with a requiresAuth key.

This flag marks routes that should only be accessible to authenticated users. To enforce this rule, add the following code just before the export default router line:

router.beforeEach(async (to, from, next) => {
 const isAuthenticated = await routeGuard();

 if (to.meta.requiresAuth && !isAuthenticated) {
 next({ name: "login" });
 } else if (to.fullPath.includes("login") && isAuthenticated) {
 next({ name: "home" });
 } else {
 next();
 }
});

This navigation guard verifies the user’s authentication status before granting access to a route. It calls the routeGuard()function from Descope to check if the user is authenticated. If a route is marked with to.meta.requiresAuth) and the user is not authenticated, they are redirected to the login page. If the user is already authenticated and tries to access the login page, they are redirected to the home page instead. In all other cases, navigation continues as normal.

Don’t forget to import the routeGuard method at the top of the same file:

import { routeGuard } from "@descope/vue-sdk";

Lastly, update the navigation bar so it displays the right buttons based on the user’s authentication status and provides a way to log out. Open src/components/NavBar.vue and replace the existing code with the following:

<script setup>
import { useDescope, useUser } from "@descope/vue-sdk";
import { ref } from "vue";
import { useRoute, useRouter } from "vue-router";

const router = useRouter();
const route = useRoute();

const { user } = useUser();
const { logout } = useDescope();

const login = () => {
 router.push({ name: "login" });
};

const handleLogout = () => {
 logout();
 router.replace({ name: "login" });
};
</script>

<template>
 <nav class="border-b p-4">
 <div class="container mx-auto flex items-center justify-between">
 <RouterLink to="/" class="text-2xl font-bold">
 JobBoard Pro
 </RouterLink>

 <div class="flex items-center space-x-4">
 <button
 v-if="!user && !route?.fullPath?.includes('login')"
 @click="login"
 class="bg-blue-600 text-white px-4 py-2 rounded-md shadow hover:bg-blue-500 transition duration-300 ease-in-out"
 >
 Get Started
 </button>

 <button
 v-if="user"
 @click="handleLogout"
 class="bg-gray-600 text-white px-4 py-2 rounded-md shadow hover:bg-gray-500 transition duration-300 ease-in-out"
 >
 Log Out
 </button>
 </div>
 </div>
 </nav>
</template>

At this point, authentication in your Vue.js app is working. Next, you’ll add RBAC to limit which users can access the Admin Dashboard.

Adding RBAC to the Vue.js application

At this point, every authenticated user can still access the Admin Dashboard. That’s not ideal—you’ll want to restrict admin functionality so only users with the proper role can use it. To achieve this, you’ll configure RBAC with Descope.

Start by opening the Descope console. From the sidebar, select Authorization, then go to the Permissions tab and click + Permission

Creating new permission
Fig: Creating new permission

In the modal, name the new permission Admin Dashboard Access, add the description “Can access the admin dashboard”, and click Add.

Next, switch to the Roles tab and click + Role to create a new one. Name it admin, use “Can perform admin functions” as the description, and assign the Admin Dashboard Access permission. 

Creating a new role
Fig: Creating a new role

Repeat the process to create a user role with the description “Can view published jobs”. Leave this role without special permissions.

Your Authorization page should now look like this:

Authorization page
Fig: Authorization page

Now you need to protect the Admin Dashboard so only users with the admin role can access it.

In src/routes/index.js, the dashboard route includes a meta.requiresAdminDashboardAccessPermission property. This flag indicates that the route requires specific permissions. To enforce this rule, replace the navigation guard you created earlier with the following:

router.beforeEach(async (to, from, next) => {
 const isAuthenticated = await routeGuard();
 const permissions = isAuthenticated && getJwtPermissions()

 if (to.meta.requiresAuth && !isAuthenticated) {
     next({ name: "login" });
 } else if (to.fullPath.includes("login") && isAuthenticated) {
     next({ name: "home" });
 } else if (to.meta.requiresAdminDashboardAccessPermission && !permissions.includes("Admin Dashboard Access")) {
     next(from.path);
 } else {
     next();
 }
});

In addition to the earlier functionality, the updated code now retrieves the authenticated user’s role permissions using the getJwtPermissions() method from the Descope SDK. It adds a condition to check whether the user has the Admin Dashboard Access permission before allowing entry to the admin route.

This ensures that only authenticated users with the correct role can access the dashboard. Be sure to update the Descope imports in this file as well:

import { getJwtPermissions, routeGuard } from "@descope/vue-sdk";

Next, open the src/views/JobsView.vue file. Right now, the page always displays a link to the admin dashboard. You’ll update it so the link only appears for users with the Admin Dashboard Access permission. To do this, add the following code to the script section to import the necessary Descope method and check the authenticated user’s permissions:

import { getJwtPermissions } from "@descope/vue-sdk";

const permissions = getJwtPermissions();

Lastly, replace the Router Link in the template so it appears only for users with the correct permission:

<RouterLink
 
 v-if="permissions?.includes('Admin Dashboard Access')"
 to="/admin"

 class="inline-block px-6 py-2 bg-blue-600 text-white font-semibold rounded-md shadow hover:bg-blue-500 transition duration-300 ease-in-out"
>
 Go to Admin Dashboard
</RouterLink>

With these changes, RBAC is fully configured. Regular users can view jobs, while only admins with the proper role can open the dashboard.

Testing the application

Before testing, make sure both servers are running. Start the Vue Vite dev server with npm run dev and the mock API with npm run dev. Then open http://localhost:5173 in your browser.

On the home page, click View Available Jobs. Since you are not authenticated, you should be redirected to the login page.

Login page
Fig: Login page

After you have provided your email address and verified using the OTP sent to your email address, you’ll be requested to provide additional information since you’re a new user:

Additional info
Fig: Additional info

An OTP will be sent to the phone number you provide, and upon successful verification, you will be redirected to the Available Jobs page:

Available jobs
Fig: Available jobs

Since your account doesn’t have the admin role, the page won’t display the Go to Admin Dashboard button. If you manually enter http://localhost:5173/admin in the browser, you’ll be redirected back.

To test admin access, open the Descope console and go to Users in the sidebar. Find the user you just created, click the actions button to edit their details, and in the user details modal, select Add Tenant / Role. Assign the admin role you set up earlier to this user.

Editing user details
Fig: Editing user details

Return to the Vue application and refresh the Available Jobs page. Now that the user has the admin role, the Go to Admin Dashboard button will appear.

Available Jobs page as an admin
Fig: Available Jobs page as an admin

When you click the button, you’ll be redirected to the Admin Dashboard page where you can publish and unpublish jobs:

Admin dashboard
Fig: Admin dashboard

If both user and admin scenarios work as described, your Vue.js authentication and RBAC setup with Descope is working correctly.

You can access the full project code on GitHub.

Vue.js authentication made simple with Descope

Authentication in Vue.js doesn’t have to be complicated. With Descope, you can implement secure login, MFA, and RBAC without building everything from scratch. By using Descope’s SDKs and flow builder, you can focus on your app’s functionality while leaving session management, token validation, and role enforcement to a purpose-built platform.

Want to add authentication to your Vue.js app without the hassle? Sign up for a Free Forever Descope account to get started. If you’d like to dive deeper into authentication best practices or see how Descope fits into your stack, book time with our auth experts today.