Blazor – Autorização com múltiplas regras (roles)

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.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Rolar para cima