I am trying to build a Blazor WASM application that can display different UI elements depending on the value(s) in the roles
claim of the token received from Azure AD.
I have a simple debug view where I iterate all claims:
@foreach(var claim in @context.User.Claims)
{
@claim
<br/>
}
Here I can clearly see the user has the following claim:
roles: ["Developer"]
This is received from the AAD app registration where I have assigned the role Developer
to my own user.
I would expect any of these statements to then return true:
context.User.HasClaim(ClaimTypes.Role, "Developer") // false
context.User.IsInRole("Developer") // false
So I wrote a custom implementation and parse the claim myself:
@if(context.User.Identity?.IsAuthenticated)
{
var rolesValue = context.User.Claims.Where(c => c.Type == "roles").First().Value; // The roles claim value is an array as a string
// Deserialize the string into a list
var roles = JsonSerializer.Deserialize<List<string>>(rolesValue);
// Print out all roles
Console.WriteLine("Roles:");
foreach(var role in roles!)
{
Console.WriteLine(role); // Prints 'Developer'
}
}
All of these snippets are run inside the following Blazor components:
<AuthorizeView>
<Authorized>
</Authorized>
</AuthorizeView>
The entire application is set up using the following tutorial.
How come I have to do this custom claim interpretation when I clearly have the claim for the user? Just Googling this issue returns so many results but I still haven't been able to solve it. The documentation here uses the method in a slightly different way, but why can I enumerate the claim in my context but still not use any of the utility methods on the User?
What am I missing here?
EDIT:
Implemented the sample from docs:
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@inject IAuthorizationService AuthorizationService
protected async override Task OnInitializedAsync()
{
var user = (await authenticationStateTask).User;
if (user.IsInRole("Developer"))
{
Console.WriteLine("Developer role from OnInitialized");
} else
{
Console.WriteLine("No role from OnInitialized"); // Always gets here, even after logging/in out or using private browsing
}
}
The enumerated claims still list the developer role in the roles claim: roles: ["Developer"]
.
Seems like there is a ton of ways to solve this depending on your auth scenario. For my scenario, I needed to know the role when authenticating in a SPA against Azure AD. The following instructions solved that.
I only made one modification to those instructions, and that was to the CustomAccountFactory
. I set the role claim that enabled me to use to built in User.IsInRole
public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
CustomUserAccount account,
RemoteAuthenticationUserOptions options)
{
var initialUser = await base.CreateUserAsync(account, options);
if (initialUser.Identity.IsAuthenticated)
{
var userIdentity = (ClaimsIdentity)initialUser.Identity;
foreach (var role in account.Roles)
{
userIdentity.AddClaim(new Claim(ClaimTypes.Role, role));
}
}
return initialUser;
}
The problem with using User.IsInRole
initially seems like I was receiving an array of roles as a string.
This could also work, but would be fragile:
User.IsInrole(@"[""Developer""]")