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.