3

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.

Darren Neimke
  • 218
  • 4
  • 18
  • You are using server side Blazor in which case you would have access to httpcontext. Your service should use HttpContext and then access Claims from the context. UserContext(IHttpContextAccessor ...) Also your github link is displaying 404. – AliK Oct 28 '21 at 06:05
  • If no user is logged in on certain pages, I create a temporary user and log them in using middleware. You might be interested to see the sig for my Invoke method: `public async Task Invoke (HttpContext context, UserManager _userManager, SignInManager _signInManager)` all that stuff gets injected into the method automatically. – Bennyboy1973 Oct 28 '21 at 06:55
  • Thanks @AliK, the repo was private. I've changed visibility so that it's public now – Darren Neimke Oct 28 '21 at 07:14
  • Hi @Bennyboy1973, according to this article, SignInManager and UserManager are not supported in Razor components. https://learn.microsoft.com/en-us/aspnet/core/blazor/security/?view=aspnetcore-5.0 I've also noticed that HttpContext (via IHttpContextAccessor) is not always available - hence the issue with my Azure hosted example – Darren Neimke Oct 28 '21 at 07:15
  • Did you manage to solve this somehow? I also have a service project similar to your idea, where I inject ClaimsPrincipal to get logged user and I can't find my way in blazor. I don't want to inject authenticationStateProvider because service project should has independency from UI. – Antonio Rodríguez Dec 22 '21 at 15:42
  • Hi @AntonioRodríguez, sorry, no... I found no solution other than injecting the AuthenticationStateProvider – Darren Neimke Dec 24 '21 at 08:53
  • 1
    Check out my solution, @DarrenNeimke, it might help you. If you need more explanation, I could expand my post or make a proyect at github. – Antonio Rodríguez Mar 14 '22 at 07:24
  • Love it, thanks for coming back to this @Antonio – Darren Neimke Mar 15 '22 at 08:36

3 Answers3

3

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.

Antonio Rodríguez
  • 976
  • 2
  • 11
  • 25
2

Use CascadingAuthenticationState to access the claims principal

https://learn.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);
}
Mayur Ekbote
  • 1,700
  • 1
  • 11
  • 14
  • 1
    `CascadingAuthenticationState` localizes the claims principal to the razor page. But I don't see how that helps with dependency injection in services? – Darren Neimke Oct 28 '21 at 07:12
  • Pass on the Claims Principal to the service in which you want to use. That is one way of using it. – Mayur Ekbote Oct 28 '21 at 10:23
  • Thanks @mayur-ekbote, that's what I was thinking... but I wasn't sure how to construct a Claims Principal to put into the DI container - especially without being able to have access to HttpContext. Any examples you can point me to? – Darren Neimke Oct 28 '21 at 11:00
  • Updated with an example code snippet. – Mayur Ekbote Oct 28 '21 at 11:14
  • Thanks @Mayur, please refer to my comment below to understand my position on using this type of approach https://stackoverflow.com/questions/69748404/how-to-inject-a-claimsprincipal-in-a-blazor-server-application/69753223#comment123336426_69753223 – Darren Neimke Oct 29 '21 at 22:16
  • 1
    See, you will not get clean architecture if you try to achieve X in a paradigm that was designed for Y. Blazor server is designed as a stateful, front end architecture. You are trying to use a pattern that is relevant for a typical REST API/MVC over http. Obviously, there would be a way of doing it, but it might look hack'y. Would you mind checking if you are getting into XY problem? – Mayur Ekbote Oct 30 '21 at 05:24
  • Thanks @Mayur, you're probably right! Interestingly if you grab the code from GitHub and run it locally in VS, it works. And it worked until recently when running in an Azure AppService as well. It makes me start to question my thinking about Blazor Server more and more though. Thanks again for helping out with this – Darren Neimke Oct 31 '21 at 01:41
2

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.

H H
  • 263,252
  • 30
  • 330
  • 514
  • 2
    Thankyou @henk, I'll take a look into this. I think I did try to use the `AuthenticationStateProvider` but I kept getting an error saying GetAuthenticationStateAsync was called before SetAuthenticationState. I'll try again and make sure my scopes are registered correctly! – Darren Neimke Oct 28 '21 at 11:06
  • Thanks @Henk, I got this to work - see example #3 in the demo app I provided. It feels a bit dirty passing the AuthenticationStateProvider around as a dependency to other services. According to the docs, it's not a favoured approach. – Darren Neimke Oct 30 '21 at 05:17