You are developing a WebAssembly authentication app and trying to implement Roles based access control. You are getting a similar error like…
- You are not authorized to access this resource.
- Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]Authorization failed. These requirements were not met: RolesAuthorizationRequirement:User.IsInRole must be true for one of the following roles: (ROLE_NAME)
The WebAssembly Authentication stack appears to cast the roles claim into a single string. We need this User Factory to modify its behavior so that each role has its own unique value.
Create the Custom User Factory
First, create a custom User Factory (CustomUserFactory.cs)…
using Microsoft.AspNetCore.Components.WebAssembly.Authentication; using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal; using System.Security.Claims; using System.Text.Json; public class CustomUserFactory : AccountClaimsPrincipalFactory<RemoteUserAccount> { public CustomUserFactory(IAccessTokenProviderAccessor accessor) : base(accessor) { } public async override ValueTask<ClaimsPrincipal> CreateUserAsync( RemoteUserAccount account, RemoteAuthenticationUserOptions options) { var user = await base.CreateUserAsync(account, options); var claimsIdentity = (ClaimsIdentity?)user.Identity; if (account != null && claimsIdentity != null) { MapArrayClaimsToMultipleSeparateClaims(account, claimsIdentity); } return user; } private void MapArrayClaimsToMultipleSeparateClaims(RemoteUserAccount account, ClaimsIdentity claimsIdentity) { foreach (var prop in account.AdditionalProperties) { var key = prop.Key; var value = prop.Value; if (value != null && (value is JsonElement element && element.ValueKind == JsonValueKind.Array)) { // Remove the Roles claim with an array value and create a separate one for each role. claimsIdentity.RemoveClaim(claimsIdentity.FindFirst(prop.Key)); var claims = element.EnumerateArray().Select(x => new Claim(prop.Key, x.ToString())); claimsIdentity.AddClaims(claims); } } } }
Add the roles mapping and CustomUserFactory to your authentication Middleware
If you’re using AddOidcAuthentication…
builder.Services.AddOidcAuthentication(options => { builder.Configuration.Bind("AzureAd", options.ProviderOptions); options.ProviderOptions.AdditionalProviderParameters.Add("domain_hint", "contoso.com"); options.ProviderOptions.DefaultScopes.Add("User.Read"); options.UserOptions.RoleClaim = "roles"; options.ProviderOptions.ResponseType = "code"; }).AddAccountClaimsPrincipalFactory<CustomUserFactory>();
If you’re using AddMsalAuthentication…
builder.Services.AddMsalAuthentication(options => { builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication); options.ProviderOptions.AdditionalScopesToConsent.Add("user.read"); options.ProviderOptions.DefaultAccessTokenScopes.Add("api://{your-api-id}"); options.UserOptions.RoleClaim = "roles"; }).AddAccountClaimsPrincipalFactory<CustomUserFactory>();
Protect your page
Now you can add the Authorize attribute to your blazor pages…
@attribute [Authorize(Roles="access_as_user")]
More Information
To learn more about WebAssembly Authentication…
Sample that has this solution implemented…
https://github.com/willfiddes/aad-webassembly-auth
Don’t forget to add App Roles to your app registration, add your user to the app role, and configure your application to use the app role you have created.