Skip to main content

Implementing role-based authorization in a Vue 3 SPA and a .NET Core API using Entra ID (formerly Azure AD) involves several steps. Here’s a detailed guide to set it up...

---
title: How to implement role-based authorization in Vue 3 using Entra ID authentication
subtitle: How can I handle token expiration and automatic token renewal in a Vue 3 SPA and a .NET Core API using Entra ID authentication?
author: Jon LaBelle
date: September 15, 2024
notoc: false
---

Implementing role-based authorization in a Vue 3 SPA and a .NET Core API using
Entra ID (formerly Azure AD) involves several steps. Here's a detailed guide to
set it up:

## Step 1: Register Applications in Entra ID

1. **Register the API**:

   - Go to the Azure portal.
   - Navigate to "Azure Active Directory" > "App registrations" > "New registration".
   - Name your application (e.g., `MyApi`).
   - Set the "Supported account types" to "Accounts in this organizational directory only".
   - Click "Register".
   - Note the "Application (client) ID" and "Directory (tenant) ID".
   - Under "Expose an API", add a scope (e.g., `api://{client-id}/access_as_user`).

2. **Define App Roles**:

   - Go to "Azure Active Directory" > "App registrations" > your API application.
   - Under "App roles", define roles (e.g., `Admin`, `User`).
   - Assign these roles to users in your Azure AD tenant.

3. **Register the SPA**:

   - Go to "Azure Active Directory" > "App registrations" > "New registration".
   - Name your application (e.g., `MySpa`).
   - Set the "Supported account types" to "Accounts in this organizational directory only".
   - Under "Redirect URI", select "Single-page application (SPA)" and enter your SPA URL (e.g., `http://localhost:8080`).
   - Click "Register".
   - Note the "Application (client) ID".
   - Under "Authentication", add the redirect URI and enable "ID tokens" and "Access tokens".
   - Under "API permissions", add the scope you defined for the API (e.g., `api://{api-client-id}/access_as_user`).

## Step 2: Configure the .NET Core API

1. **Install Required Packages**:

   ```bash
   dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
   dotnet add package Microsoft.Identity.Web
   ```

2. **Configure Authentication in `appsettings.json`**:

   ```json
   {
     "AzureAd": {
       "Instance": "https://login.microsoftonline.com/",
       "Domain": "yourdomain.onmicrosoft.com",
       "TenantId": "your-tenant-id",
       "ClientId": "your-api-client-id",
       "Audience": "api://your-api-client-id"
     }
   }
   ```

3. **Configure Authentication and Authorization in `Startup.cs`**:

   ```csharp
   using Microsoft.AspNetCore.Authentication.JwtBearer;
   using Microsoft.Identity.Web;

   public class Startup
   {
       public void ConfigureServices(IServiceCollection services)
       {
           services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
               .AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAd"));

           services.AddAuthorization(options =>
           {
               options.AddPolicy("AdminPolicy", policy =>
                   policy.RequireRole("Admin"));
               options.AddPolicy("UserPolicy", policy =>
                   policy.RequireRole("User"));
           });

           services.AddControllers();
       }

       public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
       {
           if (env.IsDevelopment())
           {
               app.UseDeveloperExceptionPage();
           }

           app.UseRouting();

           app.UseAuthentication();
           app.UseAuthorization();

           app.UseEndpoints(endpoints =>
           {
               endpoints.MapControllers();
           });
       }
   }
   ```

4. **Protect API Endpoints with Roles**:

   ```csharp
   using Microsoft.AspNetCore.Authorization;
   using Microsoft.AspNetCore.Mvc;

   [ApiController]
   [Route("api/[controller]")]
   public class ProtectedController : ControllerBase
   {
       [HttpGet("admin")]
       [Authorize(Policy = "AdminPolicy")]
       public IActionResult GetAdminData()
       {
           return Ok("This is admin data.");
       }

       [HttpGet("user")]
       [Authorize(Policy = "UserPolicy")]
       public IActionResult GetUserData()
       {
           return Ok("This is user data.");
       }
   }
   ```

## Step 3: Configure the Vue 3 SPA

1. **Install Required Packages**:

   ```bash
   npm install @azure/msal-browser @azure/msal-vue
   ```

2. **Configure MSAL in Vue**:

   ```javascript
   // src/main.js
   import { createApp } from 'vue';
   import App from './App.vue';
   import { msalPlugin } from '@azure/msal-vue';
   import { PublicClientApplication } from '@azure/msal-browser';

   const msalConfig = {
     auth: {
       clientId: 'your-spa-client-id',
       authority: 'https://login.microsoftonline.com/your-tenant-id',
       redirectUri: 'http://localhost:8080'
     },
     cache: {
       cacheLocation: 'localStorage', // This is important to persist the tokens
       storeAuthStateInCookie: true
     }
   };

   const msalInstance = new PublicClientApplication(msalConfig);

   const app = createApp(App);
   app.use(msalPlugin, msalInstance);
   app.mount('#app');
   ```

3. **Add Login and Logout Methods**:

   ```javascript
   // src/components/Login.vue
   <template>
     <div>
       <button @click="login">Login</button>
       <button @click="logout">Logout</button>
     </div>
   </template>

   <script>
   import { useMsal } from '@azure/msal-vue';

   export default {
     setup() {
       const { instance } = useMsal();

       const login = () => {
         instance.loginPopup({
           scopes: ['openid', 'profile', 'api://your-api-client-id/access_as_user']
         });
       };

       const logout = () => {
         instance.logoutPopup();
       };

       return { login, logout };
     }
   };
   </script>
   ```

4. **Check User Roles and Protect Routes**:

   ```javascript
   // src/router/index.js
   import { createRouter, createWebHistory } from 'vue-router';
   import Home from '../views/Home.vue';
   import Admin from '../views/Admin.vue';
   import { useMsal } from '@azure/msal-vue';

   const routes = [
     { path: '/', name: 'Home', component: Home },
     {
       path: '/admin',
       name: 'Admin',
       component: Admin,
       meta: { requiresAuth: true, roles: ['Admin'] }
     }
   ];

   const router = createRouter({
     history: createWebHistory(process.env.BASE_URL),
     routes
   });

   router.beforeEach(async (to, from, next) => {
     const { instance, accounts } = useMsal();
     const account = accounts.value[0];

     if (to.matched.some((record) => record.meta.requiresAuth)) {
       if (!account) {
         next('/');
       } else {
         const response = await instance.acquireTokenSilent({
           scopes: [
             'openid',
             'profile',
             'api://your-api-client-id/access_as_user'
           ],
           account: account
         });

         const token = response.idTokenClaims;
         const roles = token.roles || [];

         if (
           to.meta.roles &&
           !to.meta.roles.some((role) => roles.includes(role))
         ) {
           next('/');
         } else {
           next();
         }
       }
     } else {
       next();
     }
   });

   export default router;
   ```

5. **Display User Roles in Components**:

   ```javascript
   // src/components/UserInfo.vue
   <template>
     <div>
       <p v-if="roles.includes('Admin')">You are an Admin</p>
       <p v-else>You are a User</p>
     </div>
   </template>

   <script>
   import { useMsal } from '@azure/msal-vue';

   export default {
     setup() {
       const { accounts } = useMsal();
       const account = accounts.value[0];
       const roles = account.idTokenClaims.roles || [];

       return { roles };
     }
   };
   </script>
   ```

## Summary

1. **Register Applications in Entra ID**: Register both the API and SPA applications in Azure AD and define roles.
2. **Configure .NET Core API**: Set up JWT authentication, define authorization policies, and protect API endpoints based on roles.
3. **Configure Vue 3 SPA**: Set up MSAL for authentication, implement login and logout methods, check user roles, and protect routes based on roles.

By following these steps, you can implement role-based authorization in your Vue
3 SPA and .NET Core API using Entra ID authentication.