Reduce boiler plate code for getting the current user in a Blazor component
In a Blazor component if you want to get the current user you typically have to do something like this:
protected override async Task OnInitializedAsync()
{
var authenticationState = await AuthenticationStateTask;
CurrentUser = authenticationState.User;
}
[CascadingParameter]
public Task<AuthenticationState> AuthenticationStateTask { get; set; } = default!;
public ClaimsPrincipal CurrentUser { get; set; } = default!
There are several things going on here: a special cascading parameter, overriding a component lifecycle event, and async logic to get the auth state. It's quite a bit more complicated than the convenient User property we have in MVC & Razor Pages. And you need this code wherever you want access to the current user. Is there something we could do to cut down on the amount of boiler plate code to get the current user?
@danroth27 the reason is this way is because the Task represents the "async ness" of the login process, meaning you can write code like placeholders when the user is in the process of logging in.
I think we would in addition expose a User CascadingValue that just gives you the user and gets updated only when the user changes (not when it begins to change), and you simply have [CascadingParameter] public IClaimsPrincipal User { get; set; } whenever the user changes.
The main drawback is that you won't get an immediate notification when the users go from logged in to logged out if the logout process is async, which might have some slight security impact (being able to do something in the UI while you are logging out). But I think that's rare enough its acceptable.
Another choice might be to just always null out the user immediately when logging out, at the cost of a potential flicker in the UI (if the user for example is logging in with a different account while already logged in).
After discussing this in triage, @SteveSandersonMS has informed me that the cascading Task<AuthenticationState> does get reinitialized when the user changes, so if you were to await it in OnParameterSetAsync you could get more up-to-date authentication state than you would if you only read it in OnInitializedAsync. I gave @danroth27 bad information over Teams about this yesterday.
We do like the idea of allowing you to do something like the following:
[CascadingParameter]
public ClaimsPrincipal CurrentUser { get; set; } = default!
If the user hasn't authenticated yet, we could expose a default, non-authenticated ClaimsPrincipal. Once the user reauthenticates, we'd reset the cascading ClaimsPrincipal to an updated value and rerender.
Nice! Is there a way to we could somehow provide the property by default? Should we consider adding this as a default property on ComponentBase? 😎
I feel like the claims principle cascading through the render tree will be harder to navigate for beginners..
A simpler solution is having an @inject ICurrentUser
ICurrentUser should be both initialized in Http Request Middleware.. and, in the case of InteractiveServer, in the CircuitHandler for the interactive context.
Problem with this is it cannot natively be done in Blazor and needs an additional Scoped service in the template.