Autofac Injecting ClaimsPrincipal in Blazor Application

2

I'm facing issues with registering a service with Autofac in a Blazor application. Deep in a dependency I have a UserService which needs access to the current ClaimsPrincipal. I've wrapped that with IPrincipalProvider to avoid registering the ClaimsPrincipal directly. My issue stems from the fact that in Core the current principal isn't set on program start and the way to get the principal is to register AuthenticationStateProvider which is set by ASP.NET Core. I've gone with ServerAuthenticationStateProvider:

builder.RegisterType<ServerAuthenticationStateProvider>()
    .As<AuthenticationStateProvider>().InstancePerLifetimeScope();

This allows access to the logged on user and I can display their name on the website easily. So to get this principal registered the next entry must use a delegate in order to get the ClaimsPrincipal from the state provider using GetAuthenticationStateAsync(). Being a Task based method I figured I would just mark the lambda with async:

builder.Register(
    async c =>
    {
        var authStateProvider = c.Resolve<AuthenticationStateProvider>();
        var authState = await authStateProvider.GetAuthenticationStateAsync()
            .ConfigureAwait(false);
        return new PrincipalProvider(authState.User);
    }).As<IPrincipalProvider>();

but I get the following error:

System.ArgumentException: 'The type 'System.Threading.Tasks.Task`1[Perigee.Framework.Services.Security.PrincipalProvider]' is not assignable to service 'Perigee.Framework.Base.Services.IPrincipalProvider'.'

Given I'm newing up the PrincipalProvider explicitly I'm not really sure why it thinks it's Task. I have to await the authStateProvider.GetAuthenticationStateAsync() call so definitely need the async modifier. If I remove the async and only return new PrincipalProvider(new ClaimsPrincipal()); then the registration works and the lambda resolves at runtime (I've checked with a breakpoint in the lambda) so I know the form of what I'm doing is correct.

Can someone please point out what I'm missing in relation to making the instantiation of the PrincipalProvider not be treated as a Task?

UPDATE: Well I found a way of doing this using an async task within the Registration body:

builder.Register(
    c =>
    {
        var authStateProvider = c.Resolve<AuthenticationStateProvider>();
        var authStateTask = Task.Run(async () => await authStateProvider
            .GetAuthenticationStateAsync().ConfigureAwait(false));
        var authState = authStateTask.GetAwaiter().GetResult();
        return new PrincipalProvider(authState.User);
    }).As<IPrincipalProvider>();

Problem is:

System.InvalidOperationException
  HResult=0x80131509
  Message=GetAuthenticationStateAsync was called before SetAuthenticationState.
  Source=Microsoft.AspNetCore.Components.Server

I'm confused about why this wouldn't be available. The site is building the injectable service and passed to the [Inject] property on the page being loaded.

Anyone know how to make this work? I'd would like to put AuthenticationStateProvider as my injected service which is deep in my other dependency, but that library isn't related to ASP.NET at all. It's a CQRS based library for doing all sorts of things, mainly used for Entity Framework queries and commands so putting a dependency to an ASP.NET type makes little sense.

UPDATE AGAIN:

Thinking further registering a different implementation of IPrincipalProvider that received AuthenticationStateProvider on creation seems to be a working solution:

public class DeferredPrincipalProvider : IPrincipalProvider
{
    private readonly AuthenticationStateProvider _authenticationStateProvider;

    public DeferredPrincipalProvider(
    AuthenticationStateProvider authenticationStateProvider)
    {
        _authenticationStateProvider = authenticationStateProvider;

    }

    public ClaimsPrincipal ClaimsPrincipal
    {
        get
        {
            var authStateTask = Task.Run(async () => await _authenticationStateProvider
                .GetAuthenticationStateAsync()
                .ConfigureAwait(false));
            var authState = authStateTask.GetAwaiter().GetResult();
            return authState.User;
        }
    }

}

Which is wired up with:

builder.RegisterType<ServerAuthenticationStateProvider>().As<AuthenticationStateProvider>()
    .InstancePerLifetimeScope();
builder.RegisterType<DeferredPrincipalProvider>().As<IPrincipalProvider>();

I'm not too happy with the Task within the getter, so will switch it to an async method. GetResult() isn't intended to be called from non .NET Framework code anyway according to MS doco.

c#
asp.net-core
.net-core
dependency-injection
autofac
asked on Stack Overflow Jul 28, 2020 by Stephen York • edited Jul 29, 2020 by Steven

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0