Search code examples
c#asp.netidentityserver4blazorblazor-server-side

AuthorizeView Policy: Blazor Page not displaying


IdentityServer returns AuthenticationScheme: Bearer was forbidden.

I checked the AccessToken and it contains the scope "account.read" however blazor does not display the page

Am I missing something?

Startup.cs

        services.AddAuthorization(options =>
        {
            options.AddPolicy("Account", policy => policy.RequireClaim(JwtClaimTypes.Scope, "account"));
            options.AddPolicy("AccountWrite", policy => policy.RequireClaim(JwtClaimTypes.Scope, "account", "account.write"));
            options.AddPolicy("AccountRead", policy => policy.RequireClaim(JwtClaimTypes.Scope, "account", "account.read")); 
        });

Blazor Page

<AuthorizeView> <p> you are logged in </p> <AuthorizeView>
<AuthorizeView Policy="AccountRead">
<p> you have account.read access </p>
</AuthorizeView>

Solution

  • The issue is due to the current lack of support for Blazor to read claims the are sent as arrays.

    e.g. user: ["c","r","u","d"]

    Can't be read.

    To rectify this you need to add ClaimsPrincipalFactory.

    e.g.

    public class ArrayClaimsPrincipalFactory<TAccount> : AccountClaimsPrincipalFactory<TAccount> where TAccount : RemoteUserAccount
    {
        public ArrayClaimsPrincipalFactory(IAccessTokenProviderAccessor accessor)
        : base(accessor)
        { }
    
    
        // when a user belongs to multiple roles, IS4 returns a single claim with a serialised array of values
        // this class improves the original factory by deserializing the claims in the correct way
        public async override ValueTask<ClaimsPrincipal> CreateUserAsync(TAccount account, RemoteAuthenticationUserOptions options)
        {
            var user = await base.CreateUserAsync(account, options);
    
            var claimsIdentity = (ClaimsIdentity)user.Identity;
    
            if (account != null)
            {
                foreach (var kvp in account.AdditionalProperties)
                {
                    var name = kvp.Key;
                    var value = kvp.Value;
                    if (value != null &&
                        (value is JsonElement element && element.ValueKind == JsonValueKind.Array))
                    {
                        claimsIdentity.RemoveClaim(claimsIdentity.FindFirst(kvp.Key));
    
                        var claims = element.EnumerateArray()
                            .Select(x => new Claim(kvp.Key, x.ToString()));
    
                        claimsIdentity.AddClaims(claims);
                    }
                }
            }
    
            return user;
        }
    }
    

    Then register this in your program/startup(depending on if you use .core hosted or not)like so:

    builder.Services.AddOidcAuthentication(options =>
            {
                builder.Configuration.Bind("oidc", options.ProviderOptions);
            })
            .AddAccountClaimsPrincipalFactory<ArrayClaimsPrincipalFactory<RemoteUserAccount>>();