Reputation: 113
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.
Upvotes: 9
Views: 30516
Reputation: 7485
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
Upvotes: 11
Reputation: 113
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!
Upvotes: 2
Reputation: 796
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;
}
Upvotes: 2
Reputation: 637
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
Upvotes: 1