Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to inject service (AuthenticationStateProvider) in Blazor class

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.

like image 291
Peter Pirc Avatar asked Jan 14 '20 15:01

Peter Pirc


People also ask

How do you inject the NavigationManager in Blazor?

Access to browser navigation from Blazor is provided via the NavigationManager service. This can be injected into a Blazor component using @inject in a razor file, or the [Inject] attribute in a CS file. The NavigationManager service has two members that are of particular interest; NavigateTo and LocationChanged .

How do I use Blazor services?

If one or more common services are required by the Server and Client projects of a hosted Blazor WebAssembly solution, you can place the common service registrations in a method in the Client project and call the method to register the services in both projects. var builder = WebApplication. CreateBuilder(args); ...

How do I add Authentication to Blazor?

There is an option available to enable authentication for the Blazor app when you create the application. To enable authentication for Blazor server app, click on “Change” under “Authentication section and select “Individual User Accounts” option and then click on “Ok” button when you create a new Blazor server app.


4 Answers

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

like image 148
Stephen Byrne Avatar answered Oct 20 '22 10:10

Stephen Byrne


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!

like image 40
Peter Pirc Avatar answered Oct 20 '22 11:10

Peter Pirc


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;
}
like image 1
Dan Avatar answered Oct 20 '22 09:10

Dan


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

like image 1
Raymond A. Avatar answered Oct 20 '22 10:10

Raymond A.