Tentei utilizar regras (roles) de autorização em um aplicativo Blazor WebAssembly (WASM) e após vários testes eu deparava com um problema quando o usuário possuía mais de uma regra associada em seu token JWT.
Após várias buscas, cheguei em uma issue no GitHub do aspnet core com uma solução para esse caso, como a solução apresentou uma falha e eu consegui alterar e fazer a mesma funcionar, resolvi compartilhar por aqui, lembrando que esse foi apenas um teste inicial e ainda vou me aprofundar no caso, sempre atualizando o post.
Link para a issue:
https://github.com/dotnet/aspnetcore/issues/21836#issuecomment-630241579
De início, vamos alterar o código do arquivo Program.cs e incluir o código destacado (estou utilizando o Azure AD B2C neste caso):
using System;
using System.Net.Http;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Text;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
namespace BlazorSample
{
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAdB2C", options.ProviderOptions.Authentication);
options.ProviderOptions.LoginMode = "redirect";
options.UserOptions.RoleClaim = "roles";
});
builder.Services.AddApiAuthorization()
.AddAccountClaimsPrincipalFactory<RolesClaimsPrincipalFactory>();
await builder.Build().RunAsync();
}
}
}
A linha 29 informa o tipo da Claim que estou utilizando para trazer as regras do usuário logado.
Na linha 32 eu inclui a classe RolesClaimsPrincipalFactory, listada abaixo:
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text.Json;
using System.Threading.Tasks;
namespace BlazorSample
{
public class RolesClaimsPrincipalFactory : AccountClaimsPrincipalFactory<RemoteUserAccount>
{
public RolesClaimsPrincipalFactory(IAccessTokenProviderAccessor accessor) : base(accessor)
{
}
public override async ValueTask<ClaimsPrincipal> CreateUserAsync(RemoteUserAccount account, RemoteAuthenticationUserOptions options)
{
var user = await base.CreateUserAsync(account, options);
if (user.Identity.IsAuthenticated)
{
var identity = (ClaimsIdentity)user.Identity;
var roleClaims = identity.FindAll(identity.RoleClaimType);
if (roleClaims != null && roleClaims.Any())
{
/*
foreach (var existingClaim in roleClaims)
{
identity.RemoveClaim(existingClaim);
}
*/
var rolesElem = account.AdditionalProperties[identity.RoleClaimType];
if (rolesElem is JsonElement roles)
{
if (roles.ValueKind == JsonValueKind.Array)
{
foreach (var role in roles.EnumerateArray())
{
identity.AddClaim(new Claim(options.RoleClaim, role.GetString()));
}
}
else
{
identity.AddClaim(new Claim(options.RoleClaim, roles.GetString()));
}
}
}
}
return user;
}
}
}
As linhas que estão comentadas (de 28 a 33) são referentes ao código que modifiquei do indicado na issue, estava ocasionando um erro de alteração da collection durante a execução e, pelo que percebi, ele tentava remover a claim para não ficar duplicada. No meu teste, até o momento, essa remoção não causou nenhum problema paralelo.
Esse código \”transforma\” a lista de regras (roles) que estão em um array em uma sequência de regras únicas, replicando o tipo \”roles\” da claim inicial em várias outras entradas de claims do tipo \”roles\”, permitindo assim que o componente AuthorizeView do Blazor consiga encontrar as regras declaradas no parâmetro \”Roles\”, liberando a exibição do conteúdo somente para o usuário que realmente possa ver esse conteúdo.