Simon Bailey
Simon Bailey

Reputation: 103

Crash happening when creating custom AuthenticationStateProvider in hosted Blazor WebAssembly .net 7 app

I have a hosted Blazor WebAssembly app. The app uses the .net 7 individual accounts auth. In the client I am trying to inherit from AuthenticationStateProvider (so that I can access some of its protected functions and implement some of my own auth pages in Blazor instead or the default .cshtml ones), but I am running in to an issue whereby as soon as I do this I get a crash when creating a httpClient instance and I have no idea why its happening or how to fix it.

Here is my basic AuthenticationStateProvider derived class:

using Microsoft.AspNetCore.Components.Authorization;
using System.Net.WebSockets;
using System.Security.Claims;

namespace SiteBuddy.Client.Services
{
    public class CustomStateProvider : AuthenticationStateProvider
    {
        public async override Task<AuthenticationState> GetAuthenticationStateAsync()
        {
            var anonymous = new ClaimsIdentity();

            return await Task.FromResult(new AuthenticationState(new ClaimsPrincipal(anonymous)));
        }
    }
}

Its as minimal as I could make it and it does nothing except implement the basic required GetAuthenticationStateAsync function.

In my program.cs file I add the following in order to make sure the new class gets used:

builder.Services.AddScoped<CustomStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<CustomStateProvider>());

At the beginning of my program.cs file I have these two lines:

builder.Services.AddHttpClient("private", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
    .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
// Add this to allow us to make anonymous calls that dont require being authenticated
builder.Services.AddHttpClient("public", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress));

I create 2 HttpClients; the private one is used for almost all my back end calls where authentication is used, the private one is just used for a couple of other functions where I need to allow anonymous calls.

Further down my program.cs I have this line:

builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("private"));

As soon as I registered the CustomStateProvider class, my app crashes as soon as I try to create an instance of the 'private' HttpClient. Note that the public one is working just fine, its just the private one that causes a crash. I cant create it using the IHttpClientFactory and any time a class is created where the client is injected it crashes with the exact same error. This is the error:

Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100] Unhandled exception rendering component: Specified cast is not valid. System.InvalidCastException: Specified cast is not valid. at Microsoft.Extensions.DependencyInjection.WebAssemblyAuthenticationServiceCollectionExtensions.<>c__03[[Microsoft.AspNetCore.Components.WebAssembly.Authentication.RemoteAuthenticationState, Microsoft.AspNetCore.Components.WebAssembly.Authentication, Version=7.0.5.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[Microsoft.AspNetCore.Components.WebAssembly.Authentication.RemoteUserAccount, Microsoft.AspNetCore.Components.WebAssembly.Authentication, Version=7.0.5.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[Microsoft.AspNetCore.Components.WebAssembly.Authentication.ApiAuthorizationProviderOptions, Microsoft.AspNetCore.Components.WebAssembly.Authentication, Version=7.0.5.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].<AddRemoteAuthentication>b__0_0(IServiceProvider sp) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor2[[Microsoft.Extensions.DependencyInjection.ServiceLookup.RuntimeResolverContext, Microsoft.Extensions.DependencyInjection, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[System.Object, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].VisitCallSiteMain(ServiceCallSite callSite, RuntimeResolverContext argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor2[[Microsoft.Extensions.DependencyInjection.ServiceLookup.RuntimeResolverContext, Microsoft.Extensions.DependencyInjection, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[System.Object, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].VisitCallSite(ServiceCallSite callSite, RuntimeResolverContext argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor2[[Microsoft.Extensions.DependencyInjection.ServiceLookup.RuntimeResolverContext, Microsoft.Extensions.DependencyInjection, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[System.Object, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].VisitCallSiteMain(ServiceCallSite callSite, RuntimeResolverContext argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitDisposeCache(ServiceCallSite transientCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor2[[Microsoft.Extensions.DependencyInjection.ServiceLookup.RuntimeResolverContext, Microsoft.Extensions.DependencyInjection, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60],[System.Object, System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].VisitCallSite(ServiceCallSite callSite, RuntimeResolverContext argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope) at Microsoft.Extensions.DependencyInjection.ServiceLookup.RuntimeServiceProviderEngine.<>c__DisplayClass4_0.<RealizeService>b__0(ServiceProviderEngineScope scope) at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope) at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType) at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType) at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[BaseAddressAuthorizationMessageHandler](IServiceProvider provider) at Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.<>c__41[[Microsoft.AspNetCore.Components.WebAssembly.Authentication.BaseAddressAuthorizationMessageHandler, Microsoft.AspNetCore.Components.WebAssembly.Authentication, Version=7.0.5.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].b__4_1(HttpMessageHandlerBuilder b)

It appears to be some kind of circular dependency issue, but I dont understand how that can be happening when I have added practically no code at all to CustomStateProvider.

Can anyone cast any light on what the problem is?

Upvotes: 0

Views: 193

Answers (1)

MrC aka Shaun Curtis
MrC aka Shaun Curtis

Reputation: 30167

You have problems with both inheritance and registration.

Here's an example custom provider. Note adding your extra stuff to the provided ClaimsPrincipal.

public class CustomAuthenticationStateProvider : RemoteAuthenticationStateProvider
{
    public override async Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        var authState = await base.GetAuthenticationStateAsync();
        var user = authState.User;
        
        // Get your custom data - in this case some roles

        // add some new identities to the Claims Principal
        user.AddIdentity(new ClaimsIdentity(new List<Claim>() { new Claim(ClaimTypes.Role, "Admin") }));
        user.AddIdentity(new ClaimsIdentity(new List<Claim>() { new Claim(ClaimTypes.Role, "User") }));

        // return the modified principal
        return await Task.FromResult(new AuthenticationState(user));
    }
}

Which you register like this:


//other registrations

//Last one so it overrides the default loaded provider
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();

Upvotes: 0

Related Questions