6

I'm struggling to inject a service (AuthenticationStateProvider) in a class in Blazor server. If I do it in a razor component, it is pretty simple:

@inject AuthenticationStateProvider AuthenticationStateProvider

and then

private async Task LogUsername()
{
    var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
    var user = authState.User;

    if (user.Identity.IsAuthenticated)
    {
       ClientMachineName = $"{user.Identity.Name}";
    }
    else
    {
       ClientMachineName = "Unknown";
    }
} 

However I need to do this, i.e. retrieve the authenticated user machine name, in a class instead of a razor component.

I tried for instance:

[Inject]
AuthenticationStateProvider AuthenticationStateProvider { get; set; }

public async Task LogUsername()
{        
    var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
    var user = authState.User;

    if (user.Identity.IsAuthenticated)
    {
        ClientMachineName = $"{user.Identity.Name}";
    }
    else
    {
        ClientMachineName = "Unknown";
    }
}

But this does not seem to work.

Any help would be much appreciated.

ˈvɔlə
  • 9,204
  • 10
  • 63
  • 89
Peter
  • 83
  • 1
  • 1
  • 6

4 Answers4

10

with Blazor server (.Net Core 3), this worked for me:

public class AuthTest
{
    private readonly AuthenticationStateProvider _authenticationStateProvider;

    public AuthTest(AuthenticationStateProvider authenticationStateProvider)
    {
        _authenticationStateProvider = authenticationStateProvider;
    }

    public async Task<IIdentity> GetIdentity()
    {
        var authState = await _authenticationStateProvider.GetAuthenticationStateAsync();
        var user = authState.User;
        return user.Identity;
    }
}

You need to register this with the ASP.Net Core DI in Startup.ConfigureServices:

services.AddScoped<AuthTest>();

And then inject it on your .razor page:

@page "/AuthTest"
@inject AuthTest authTest;
<button @onclick="@LogUsername">Write user info to console</button>

@code{
    private async Task LogUsername()
    {
        var identity= await authTest.IsAuthenticated();
        Console.WriteLine(identity.Name);
    }

You should see the logged-in username written to the ASP.Net output console.

Update If you want to get the currently logged in user from within a separate class and you're not injecting that onto a blazor page, then follow the guidance here

Stephen Byrne
  • 7,400
  • 1
  • 31
  • 51
  • 1
    Thanks Stephen - but my problem is that I would like to consume this 'AuthTest' service from another class, and not from a .razor page (your last step). Essentially I would like to get the identity.Name output in a .cs file (my user service where I use this username to retrieve information about the user). Any idea how to do that? Thank you – Peter Jan 14 '20 at 16:19
  • @PeterPirc - oh I see, my bad.OK, can you describe how it doesn't work? – Stephen Byrne Jan 14 '20 at 16:43
  • @StephenByrne thank you for pointing me in the right direction but whenever I do this my httpContext always returns null (I have registered the service). I am not sure what's going on... any advice? For clarification, I am using a server side application with windows authentication; I just want to access the claims made in it in my service classes (IE, the login/email of the authenticated user). – Dan Jan 14 '20 at 23:11
  • @PeterPirc - so just to clarify something, what's the entry point for this code, is it the blazor code section or is it a WebApi controller? – Stephen Byrne Jan 15 '20 at 13:27
  • Hello @StephenByrne - thanks a lot for helping out with this, much appreciated. I need to play with this a little bit more; however I believe that the httpContextAccessor retrieves the server username and not the client username. I will investigate more and report back. Also it seems like the answer Dan provided below might be the solution to my issues. – Peter Jan 15 '20 at 16:06
  • 1
    You cannot use `IHttpContextAccessor` in Blazor - see https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-context?view=aspnetcore-6.0#blazor-and-shared-state – AndrewL Nov 18 '21 at 18:18
  • @AndrewL - yes it certainly seems like a bad idea from that linked article...honestly cannot recall if that was the guidance at the time. Anyways, thanks for pointing it out, I'll update this answer to reflect. – Stephen Byrne Nov 19 '21 at 09:08
2

Check out the solution I had to this problem here:

Accessinging an authenticated user outside of a view in Blazor

This should solve your problem.

Edit: If you would like to get the information about the authentication state, what you should do is create a claim on the authentication state with the username or whatever detail you require in it, instead of creating a class and assigning the name to that. That way, in classes that need this information you can just inject a service class that gets all of the claims on the current authentication state. This really should all be done in a custom authentication state provider.

Example:

public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
    MyUser = //DB call to get user information
    var claimsIdentity = new ClaimsIdentity(new[] { new 
    Claim(ClaimTypes.Name, MyUser.Name) }, "Authenticated");
    var user = new ClaimsPrincipal(identity);
    return new AuthenticationState(user);
}

Then in another service you would get the claims with the user information in it and inject that into any other service/class the information is needed.

public ApplicationUser(AuthenticationStateProvider authenticationStateProvider)
{
    _authenticationStateProvider = authenticationStateProvider;
}

public async Task<string> GetLogin()
{
    var authState = await _authenticationStateProvider.GetAuthenticationStateAsync();
    return authState.User.Claims.Where(c => c.Type == ClaimTypes.Name).FirstOrDefault().Value;
}
ˈvɔlə
  • 9,204
  • 10
  • 63
  • 89
Dan
  • 746
  • 1
  • 9
  • 14
  • this might be it! I cannot comment in your other post (low reputation:) but can you pls let me know what is the syntax for this: "inject the AuthenticationStateProvider into your service class" – Peter Jan 15 '20 at 16:07
  • Sure thing! Basically it's the code I have in the link... In the class you are trying to use the provider in, you need to create a constructor for it that takes AuthenticationStateProvider as a parameter. You then assign that to the readonly _authenticationStateProvider in the class and use that to do your operations. Does this make sense? If not, let me know! – Dan Jan 15 '20 at 16:37
  • Edited answer...this should do it... I just implemented the same thing in my application. – Dan Jan 17 '20 at 17:42
2

Thanks again both @StephenByrne and @Dan - I'm almost there now with my requirements. This is my user service class and it works as expected:

public class AuthUser
{

    private readonly AuthenticationStateProvider _authenticationStateProvider;

    public AuthUser(AuthenticationStateProvider authenticationStateProvider)
    {
        _authenticationStateProvider = authenticationStateProvider;
        var username = _authenticationStateProvider.GetAuthenticationStateAsync().Result;
        FetchMyUser(username.User.Identity.Name);
    }

    public User MyUser { get; set; }

    public void FetchMyUser(string machineName = "Unknown")
    {
        using (IDbConnection connection = new System.Data.SqlClient.SqlConnection(SettingsService.DBConnectionString2016))
        {
            MyUser = connection.QueryFirstOrDefault<User>($"SELECT FirstName FROM MyTable WHERE MachineName = '{machineName}' ;");
        }
    }
}

And then in Startup.cs I add this service as Scoped (this is important, as Dan pointed out below);

services.AddScoped<AuthUser>();

I can then use this service from a .razor component as follows:

@inject AuthUser authUser

Hello @authUser.MyUser.FirstName

The only remaining issue I have is that I don't know how to consume this service in another .cs class. I believe I should not simply create an object of that class (to which I would need to pass the authenticationStateProvider parameter) - that doesn't make much sense. Any idea how I could achive the same as I mentioned in the .razor file but in a .cs class instead ?

Thanks!

ˈvɔlə
  • 9,204
  • 10
  • 63
  • 89
Peter
  • 83
  • 1
  • 1
  • 6
  • I don't use the `authenticationStateProvider` very much in code and would like to use `_authenticationStateProvider.GetAuthenticationStateAsync().Result;` like in this answer. If `.Result` is used minimally, is it an acceptable practice? – user3071284 Nov 19 '20 at 14:32
1

if you in your startup.cs add some services

services.AddScoped<TokenProvider>();
services.AddTransient<TokenRefreshService>();
services.Add<GraphServiceService>();

you can in a razor page inject them by their type

@inject TokenProvider _token
@inject TokenRefreshService _tokenrefresh
@inject GraphServiceService _graphservice

These service classes, you inject them in throught the constructor

public GraphServiceClass(AuthenticationStateProvider _AuthenticationStateProvider, TokenProvider _token)
{
    AuthenticationStateProvider = _AuthenticationStateProvider;
    token = _token;
}

I recommend this: ASP.NET Core Blazor dependency injection

Raymond A.
  • 496
  • 7
  • 18