VOOZH about

URL: https://dev.to/mattia_armas/role-based-access-control-in-blazor-webassembly-with-azure-ad-5gp5

⇱ Role-Based Access Control in Blazor WebAssembly with Azure AD - DEV Community


Blazor WebAssembly runs entirely in the browser. That single fact shapes everything about how you implement authorization, because nothing the client decides can be trusted. A user can open dev tools, edit memory, and flip any boolean you use to hide a button.

So role-based access control in a WASM app is really two separate jobs:

  1. Cosmetic — show users only the parts of the UI they're allowed to use, so the app feels coherent.
  2. Enforced — make sure the API rejects anything a user shouldn't be able to do, regardless of what the client sends.

This post covers both, driven from Azure AD app roles.

Step 1: Define app roles in Azure AD

In the Azure portal, open your app registration → App roles → create a role. The important field is the Value — that's the string that lands in the token. For example, a role with value BasicUser.

Then assign users to that role under Enterprise applications → your app → Users and groups. Azure AD will now include the role in the roles claim of the access token issued to that user.

Step 2: Map the role claim in the client

Blazor WASM doesn't automatically know that the roles claim should map to .NET role checks. You tell it during authentication setup:

builder.Services.AddMsalAuthentication(options =>
{
 builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
 options.ProviderOptions.DefaultAccessTokenScopes.Add("api://your-api-id/access_as_user");

 // Make the "roles" claim drive IsInRole / [Authorize(Roles = ...)]
 options.UserOptions.RoleClaim = "roles";
});

With that mapping in place, the standard authorization primitives start working off your Azure AD roles.

Step 3: Conditional UI with AuthorizeView

For showing and hiding pieces of UI, AuthorizeView is the cleanest tool:



 Run report


 You don't have access to this feature.


This is the cosmetic layer. It's genuinely useful — it stops users from being confused by controls they can't use — but on its own it secures nothing.

Step 4: Restrict navigation

A common pattern is to hide whole sections of the nav menu. You can check roles imperatively by injecting the authentication state:

@inject AuthenticationStateProvider AuthState

@if (_isBasicUser)
{
 Reports
}

@code {
 private bool _isBasicUser;

 protected override async Task OnInitializedAsync()
 {
 var state = await AuthState.GetAuthenticationStateAsync();
 _isBasicUser = state.User.IsInRole("BasicUser");
 }
}

You can also protect the routed pages themselves with an attribute, so that even a user who types the URL directly gets bounced to the "not authorized" view:

@page "/reports"
@attribute [Authorize(Roles = "BasicUser")]

Again — useful, but still client-side. A determined user can bypass all of it.

Step 5: The part that actually matters — enforce on the server

Every endpoint behind the UI must independently check the role. The browser-side checks are a convenience; the API is the boundary that counts.

[ApiController]
[Route("api/reports")]
public class ReportsController : ControllerBase
{
 [HttpPost("run")]
 [Authorize(Roles = "BasicUser")]
 public async Task RunReport()
 {
 // Only reachable by a token that actually carries the role.
 // ...
 return Ok();
 }
}

Because the same Azure AD token carries the same roles claim to the API, the server validates the role from a source the client can't forge. If someone strips the client-side checks and calls the endpoint directly, the [Authorize] attribute rejects them.

The mental model to take away

Think of it as defense in two layers with very different jobs:

  • The Blazor WASM layer makes the app pleasant and coherent — users see what's relevant to them.
  • The API layer makes the app secure — it assumes the client is hostile and validates every role on its own.

If you only do the client side, you have a UI that looks locked down and an API that's wide open. If you only do the server side, you have a secure app with a confusing UI full of buttons that error out. You want both, and it's worth being explicit about which layer you're working on at any given moment — because they're easy to conflate, and conflating them is exactly how WASM apps end up insecure.