How to inject a ClaimsPrincipal in a Blazor Server application

Viewed 1990

Here are some artifacts to help understand the issue:

  • Sample Code - Github repo
  • Deployed Application - no longer available

Update: I have followed this YouTube video which I now believe to be the correct way of accessing information about the authenticated user in dependent services for a Blazor Server application: https://www.youtube.com/watch?v=Eh4xPgP5PsM.

I've updated the Github code to reflect that solution.


I have the following classes that I register using dependency injection in my ASP.NET MVC Core application.

public class UserContext
{
    ClaimsPrincipal _principal;

    public UserContext(ClaimsPrincipal principal) => _principal = principal;

    public bool IsAuthenticated => _principal.Identity.IsAuthenticated;
}


public class WrapperService
{
   UserContext _userContext; 

   public WrapperService(UserContext context) => _userContext = context;

   public bool UserHasSpecialAccess() 
   {
        return _userContext.IsAuthenticated;
   }
}

The IoC dependency registrations are configured in Startup.cs

services.AddScoped<ClaimsPrincipal>(x =>
{
    var context = x.GetRequiredService<IHttpContextAccessor>();
    return context.HttpContext.User;
});

services.AddScoped<UserContext>();
services.AddScoped<WrapperService>();

I recently enabled Blazor in the MVC application and wanted to use my DI registered services from within my Blazor components.

I injected the service in a Blazor component in order to use it like so:

@inject WrapperService _Wrapper

However, when I attempt to use the service from a server side handler, the request fails with an exception complaining that the services could not be constructed - due to IHttpContext not existing on subsequent calls to the server.

<button @onclick="HandleClick">Check Access</button>

async Task HandleClick()
{
    var hasPermission = _Wrapper.UserHasSpecialAccess(); // fails 
}

I think I understand why the use of IHttpContextAccessor is not working/recommended in Blazor Server apps. My question is, how can I access the claims I need in my services without it?

The odd thing to me is that this all works when I run it under IIS Express in my development environment, but fails when I deploy and attempt to run it from within an Azure AppService.

3 Answers

You can inject AuthenticationStateProvider into your Service constructor and then use

var principal = await _authenticationStateProvider.GetAuthenticationStateAsync();

AuthenticationStateProvider is a Scoped service so yours has to be too.

This is what work for me, writing a derived class for AuthenticationStateProvider.

public class AppAuthenticationStateProvider : AuthenticationStateProvider
{
    private ClaimsPrincipal principal;

    // Constructor, only needed when injections required
    public AppAuthenticationStateProvider(/* INJECTIONS HERE */)
        : base()
    {
        principal ??= new();
    }

    public override Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        return Task.FromResult(new AuthenticationState(principal));
    }

    // Method called from login form view
    public async Task LogIn(/* USER AND PASSWORD */)
    {
        // Create session
        principal = new ClaimsPrincipal(...);
        var task = Task.FromResult(new AuthenticationState(principal));
        NotifyAuthenticationStateChanged(task);
    }

    // Method called from logout form view
    public async Task LogOut()
    {
        // Close session
        principal = new();
        var task = Task.FromResult(new AuthenticationState(principal));
        NotifyAuthenticationStateChanged(task);
    }

Then, at program/startup you add these lines:

// Example for .Net 6
builder.Services.AddScoped<AuthenticationStateProvider, AppAuthenticationStateProvider>();
builder.Services.AddScoped<ClaimsPrincipal>(s =>
{
    var stateprovider = s.GetRequiredService<AuthenticationStateProvider>();
    var state = stateprovider.GetAuthenticationStateAsync().Result;
    return state.User;
});

That's it. Now you can inject ClaimsPrincipal wherever you want.

Use CascadingAuthenticationState to access the claims principal

https://docs.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-5.0#expose-the-authentication-state-as-a-cascading-parameter-1

If you need to use your own logic, you will need to implement your own authentication state provider.

If you want to use a service to use ClaimsPrincipal you can do the following:

ClaimsPrincipalUserService.cs


ClaimsPrincipal claimsPrincipal;
void SetClaimsPrincipal(ClaimsPrincipal cp)
{
   claimsPrincipal = cp;
  // any logic + notifications which need to be raised when 
  // ClaimsPrincipal has changes
}

Inject this service as scoped in the startup.

In the layout

MainLayout.razor

@inject ClaimsPrincipalUserService cpus;

[CascadingParameter]
public Task<AuthenticationState> State {get;set;}

protected override async Task OnInitializedAsync()
{
   var state = await State;
   var user = state.User; // Get claims principal.
   cpus.SetClaimsPrincipal(user);
}
Related