Search code examples
c#blazor-webassemblyazure-ad-msal

Blazor WASM application ClaimsPrinicpal and roles claim


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"].


Solution

  • 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""]")