How to inject service (AuthenticationStateProvider) in Blazor class

11,103

Solution 1

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;
}

Solution 2

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

Solution 3

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!

Share:
11,103
Peter Pirc
Author by

Peter Pirc

Updated on June 09, 2022

Comments

  • Peter Pirc
    Peter Pirc almost 2 years

    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.