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.