The is for a Blazor (server side) application. As I understand it, I have three objects for a logged in user:
AuthenticationState
which I can get in my razor page as it's injected. This allows me to make calls to see if claims allow users to take actions or see data.IdentityUser
which is the ASP.NET Identity user which has their email, phone etc. I can get that using the code below.User.AspUserId == IdentityUser.Id
so I can read that object once I have the IdentityUser
.private async Task<IdentityUser?> GetUser()
{
var userPrincipal = (await authenticationStateTask).User;
var userid = userPrincipal
.FindFirst(u => u.Type.Contains("nameidentifier"))?
.Value;
if (!string.IsNullOrEmpty(userid))
return await UserDbContext.Users.FirstOrDefaultAsync(u => u.Id == userid);
return null;
}
My questions are:
AuthenticationStateProvider.AuthenticationStateChanged
event. Should I set this up and use it to re-read my IdentityUser
and User
object and otherwise I can keep those objects without re-reading them per page.GetUser()
code above the best way to get the IdentityUser
?Anything I'm missing in all this?
To answer all your questions, you need to consider where you need to consume the current user.
In a component, that's pretty simple. You can consume the Task<AuthenticationState>
cascaded parameter. It's a Task
, but will almost always be in the Completed
state, and return immediately.
If however you need the user context in another service you need to use the registered AuthenticationStateProvider
service.
Here's a simple User wrapper Service that can be consumed by Services or components.
public class UserService : IDisposable
{
private readonly AuthenticationStateProvider _stateProvider;
private Task _task = Task.CompletedTask;
public event EventHandler<UserChangedEventArgs>? UserChanged;
public ClaimsPrincipal User { get; set; } = new ClaimsPrincipal();
public UserService(AuthenticationStateProvider authenticationStateProvider)
{
_stateProvider = authenticationStateProvider;
_stateProvider.AuthenticationStateChanged += this.OnUserChanged;
// get the current user. as this is an async method we just assign it to a class variable
_task = this.GetUserAsync(_stateProvider.GetAuthenticationStateAsync());
}
public async ValueTask<ClaimsPrincipal> GetUserAsync()
{
// probably complete, but we don't know for sure so always await it
await _task;
return User;
}
private void OnUserChanged(Task<AuthenticationState> task)
{
// As this is a fire and forget event we assign the new task to our class task
// which we can await if we need to
_task = this.GetChangedUserAsync(task);
}
private async Task GetChangedUserAsync(Task<AuthenticationState> task)
{
// get the user
await this.GetUserAsync(task);
// raise our fire and forget event now we've got the new user
this.UserChanged?.Invoke(this, new UserChangedEventArgs(User));
}
private async Task GetUserAsync(Task<AuthenticationState> task)
{
// Reset the user to a new ClaimsPrincipal which is effectivity no user
this.User = new ClaimsPrincipal();
// get the state object
var state = await task;
// update the user
if (state is not null)
this.User = state.User;
}
public void Dispose()
=> _stateProvider.AuthenticationStateChanged -= this.OnUserChanged;
}
public class UserChangedEventArgs : EventArgs
{
public ClaimsPrincipal? User { get; private set; }
public UserChangedEventArgs(ClaimsPrincipal? user)
=> User = user;
}
For reference, here's the code for CascadingAuthenticationState
:
@implements IDisposable
@inject AuthenticationStateProvider AuthenticationStateProvider
<CascadingValue TValue="System.Threading.Tasks.Task<AuthenticationState>" Value="@_currentAuthenticationStateTask" ChildContent="@ChildContent" />
@code {
private Task<AuthenticationState>? _currentAuthenticationStateTask;
/// <summary>
/// The content to which the authentication state should be provided.
/// </summary>
[Parameter]
public RenderFragment? ChildContent { get; set; }
protected override void OnInitialized()
{
AuthenticationStateProvider.AuthenticationStateChanged += OnAuthenticationStateChanged;
_currentAuthenticationStateTask = AuthenticationStateProvider
.GetAuthenticationStateAsync();
}
private void OnAuthenticationStateChanged(Task<AuthenticationState> newAuthStateTask)
{
_ = InvokeAsync(() =>
{
_currentAuthenticationStateTask = newAuthStateTask;
StateHasChanged();
});
}
void IDisposable.Dispose()
{
AuthenticationStateProvider.AuthenticationStateChanged -= OnAuthenticationStateChanged;
}
}
You see see the original here - https://github.com/dotnet/aspnetcore/blob/main/src/Components/Authorization/src/CascadingAuthenticationState.razor