Search code examples
authorizationblazorpolicywebassembly

Policy base authoriraztion in blazor wasm


I am trying to implement policy based authorization with roles from database.

Server side:

services.AddAuthorizationCore(config =>
{
    var context = services
        .BuildServiceProvider()
        .GetService<DbContext>();

    var policies = context.ApplicationPolicies
        .Include(x => x.PolicyRoles)
        .ThenInclude(x => x.Role)
        .ToList();

    foreach (var policy in policies)
    {
        config.AddPolicy(policy.Name, policyBuilder =>
        {
            policyBuilder.RequireRole(policy.PolicyRoles.Select(x => x.Role.Name));
        });
    }
});

Client Side:

public class Program
{
    public static async Task Main(string[] args)
    {
        var builder = WebAssemblyHostBuilder.CreateDefault(args);
        builder.RootComponents.Add<App>("app");

        builder.Services.AddHttpClient("MyApp.ServerAPI",
            client => client.BaseAddress = new
            Uri(builder.HostEnvironment.BaseAddress)) 
            .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

        builder.Services.AddTransient(sp => 
            sp.GetRequiredService<IHttpClientFactory>()
            .CreateClient("MyApp.ServerAPI"));

        builder.Services.AddScoped<Radzen.NotificationService>();

        builder.Services.AddApiAuthorization()
            .AddAccountClaimsPrincipalFactory<ExtendedAccountClaimsFactory>();


        builder.Services.AddAuthorizationCore(config =>
        {
            var httpClient = builder
                .Services.BuildServiceProvider()
                .GetService<HttpClient>();

            var policies =  await httpClient
                .GetFromJsonAsync<List<ApplicationPolicies>>  
                ("ApplicationPolicies");

            foreach (var policy in policies)
            {
                config.AddPolicy(policy.Name, policyBuilder =>
                {
                    policyBuilder.RequireRole(
                        policy.PolicyRoles.Select(x => x.Role.Name)
                    );
                });
            }
        });

        await builder.Build().RunAsync();
    }
}

Here the server side code works fine, but the client side throws AccessTokenNotAvailableException:

Am I missing something.


Solution

  • Your issue is here :

                builder.Services.AddHttpClient("MyApp.ServerAPI",
                            client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
                            .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
    
                // each HttpClient use the BaseAddressAuthorizationMessageHandle
                builder.Services.AddTransient(sp => sp.GetRequiredService<IHttpClientFactory>()
                                                        .CreateClient("MyApp.ServerAPI"));
    

    Each HttpClient use the BaseAddressAuthorizationMessageHandler but the user is not yet authentified when you request for policies here:

                builder.Services.AddAuthorizationCore(config =>
                {
                    var httpClient = builder.Services.BuildServiceProvider().GetService<HttpClient>();
                    var policies =  await httpClient.GetFromJsonAsync<List<ApplicationPolicies>>("ApplicationPolicies");
                    foreach (var policy in policies)
                            {
                                config.AddPolicy(policy.Name, policyBuilder =>
                                {
                                    policyBuilder.RequireRole(policy.PolicyRoles.Select(x => x.Role.Name));
                                });
                            }
                });
    

    Instead use a different HttpClient:

                builder.Services.AddAuthorizationCore(config =>
                {
                    var httpClient = new HttpClient
                    {
                        BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)   
                    };
                    var policies =  await httpClient.GetFromJsonAsync<List<ApplicationPolicies>>("ApplicationPolicies");
                    foreach (var policy in policies)
                            {
                                config.AddPolicy(policy.Name, policyBuilder =>
                                {
                                    policyBuilder.RequireRole(policy.PolicyRoles.Select(x => x.Role.Name));
                                });
                            }
                });