AuthenticationStateProvider propagates incomplete identity to WebAssembly with Blazor in .NET 8

469 Views Asked by At

I have a component using auto render mode:

@attribute [RenderModeInteractiveAuto]

I inject the AuthenticationStateProvider:

@inject Microsoft.AspNetCore.Components.Authorization.AuthenticationStateProvider _auth

Now, in my code, if I do something like this:

var state = await _auth.GetAuthenticationStateAsync();
var user = state.User;
var isAdmin = user.IsInRole("admin");

When this is ran on the server, isAdmin is true when my logged-in user is in the admin role. However, on WASM, this is false. Plus, on the server side, the name claim correctly contains the user name whereas in WASM it contains the e-mail address.

Is this still a bug or do I have to tweak some settings to get the correct identity propagated?

1

There are 1 best solutions below

0
On BEST ANSWER

Authentication in Blazor 8 WASM is performed by PersistingServerAuthenticationStateProvider on the server persisting the Identity using the UserInfo class to the wasm client via its PersistentAuthenticationStateProvider

I achieved this and forced everyone to be an "Administrator" for demonstration/testing purposes as follows:

Updated UserInfo to include Roles

public class UserInfo
{
    public required string UserId { get; set; }
    public required string Email { get; set; }
    public required UserRole[]? Roles { get; set; }
}

public class UserRole
{
    public required string Name { get; set; }
}

On the server project update PersistingServerAuthenticationStateProvider.cs

Just below these two lines

var userId = principal.FindFirst(_options.ClaimsIdentity.UserIdClaimType)?.Value;
var email = principal.FindFirst(_options.ClaimsIdentity.EmailClaimType)?.Value;

I added this to persist the role claims

UserRole[] userRoles =
    principal.FindAll(_options.ClaimsIdentity.RoleClaimType)
    .Select(a=> new UserRole { Name = a.Value })
    .Append(new UserRole { Name = "Administrator" }) // <== remove this
    .ToArray();

Then in the client to create the ClaimsIdentity update PersistentAuthenticationStateProvider.cs

public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
    if (!persistentState.TryTakeFromJson<UserInfo>(nameof(UserInfo), out var userInfo) || userInfo is null)
    {
        return _unauthenticatedTask;
    }

    List<Claim> claims = [
        new Claim(ClaimTypes.NameIdentifier, userInfo.UserId),
        new Claim(ClaimTypes.Name, userInfo.Email), // <== Name Claim Issue
        new Claim(ClaimTypes.Email, userInfo.Email)];

    foreach (var role in userInfo?.Roles ?? [])
    {
        claims.Add(new Claim(ClaimTypes.Role, role.Name));
    }

    return Task.FromResult(
        new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(claims.ToArray(),
            authenticationType: nameof(PersistentAuthenticationStateProvider)))));
}

@page "/auth"

@using Microsoft.AspNetCore.Authorization

@attribute [Authorize]
@attribute [RenderModeInteractiveWebAssembly]

<PageTitle>Auth</PageTitle>

<h1>You are authenticated</h1>

<AuthorizeView>
    Hello @context.User.Identity?.Name!
</AuthorizeView>
<AuthorizeView Roles="Administrator">
    [Administrator]
</AuthorizeView>