Custom AuthenticationStateProvider Unexpected Task<AuthenticationState> / Odd behavior
- Dominant language
- C#
- Stars
- 38.4k
- Forks
- 10.9k
- Avg merge
- 2d 6h
- Merged PRs (30d)
- 290
Description
### Is there an existing issue for this?
- [X] I have searched the existing issues
### Describe the bug
Running into some weird/unexpected states with implementing a custom AuthenticationStateProvider for Blazor. The CustomAuthProvider is a simple class that sets the `Task` to the static `GuestAuthenticationState` in the ctor. I'm finding that what should be identitical DI registrations is resultilng in different equality references. Maybe I'm overlooking something very simple here but the equality checks on AuthenticationState are not making sense:
CustomAuthProvider:
```c#
public class CustomAuthProvider : ServerAuthenticationStateProvider
{
public readonly static ClaimsPrincipal GuestClaims = new ClaimsPrincipal(new ClaimsIdentity("123"));
public readonly static AuthenticationState Guest = new AuthenticationState(GuestClaims);
public readonly static Task GuestAuthenticationState = Task.FromResult(Guest);
public string Id = Guid.NewGuid().ToString();
public CustomAuthProvider()
{
SetAuthenticationState(GuestAuthenticationState); //sets the task to the static GuestAuthenticationState
}
}
```
DI Registration case 1:
```c#
builder.Services.AddServerSideBlazor();
builder.Services.AddScoped();
builder.Services.AddScoped(p => p.GetRequiredService());
```
DI Registration case 2:
```c#
builder.Services.AddServerSideBlazor();
builder.Services.AddScoped();
builder.Services.AddScoped();
```
In DI registration Case 1, this is how the equality works out:
```c#
[Inject] private CustomAuthProvider _blazorInjectedAuthProvider{ get; set; }
[Inject] private IServiceProvider _sp { get; set; }
[CascadingParameter]
private Task cascadingAuthStateTask{ get; set; }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
//builder.Services.AddScoped();
//builder.Services.AddScoped(p => p.GetRequiredService());
var scope = _sp.CreateScope();
var baseAuthProvider = scope.ServiceProvider.GetRequiredService();
var customerAuthProvider = scope.ServiceProvider.CreateScope().ServiceProvider.GetRequiredService();
if (await cascadingAuthStateTask== CustomAuthProvider.Guest)
{
Debugger.Break(); //never breaks
}
if (await authenticationStateTask == await _authProvider.GetAuthenticationStateAsync())
{
Debugger.Break(); //breaks as expected
}
if (await baseAuthProvider.GetAuthenticationStateAsync() == CustomAuthProvider.Guest)
{
Debugger.Break(); //breaks as expected
}
if (await customerAuthProvider.GetAuthenticationStateAsync() == CustomAuthProvider.Guest)
{
Debugger.Break(); //breaks as expected
}
if (await _blazorInjectedAuthProvider.GetAuthenticationStateAsync() == CustomAuthProvider.Guest)
{
Debugger.Break(); //never breaks
}
}
```
In DI registration Case 2, this is how the equality works out:
```c#
[Inject] private CustomAuthProvider _blazorInjectedAuthProvider{ get; set; }
[Inject] private IServiceProvider _sp { get; set; }
[CascadingParameter]
private Task cascadingAuthStateTask{ get; set; }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
//builder.Services.AddScoped();
//builder.Services.AddScoped();
var scope = _sp.CreateScope();
var baseAuthProvider = scope.ServiceProvider.GetRequiredService();
var customAuthProvider = scope.ServiceProvider.CreateScope().ServiceProvider.GetRequiredService();
if (await cascadingAuthStateTask== CustomAuthProvider.Guest)
{
Debugger.Break(); //never breaks
}
if (await authenticationStateTask == await _authProvider.GetAuthenticationStateAsync())
{
Debugger.Break(); //never breaks
}
if (await baseAuthProvider.GetAuthenticationStateAsync() == CustomAuthProvider.Guest)
{
Debugger.Break(); //breaks as expected
}
if (await customAuthProvider.GetAuthenticationStateAsync() == CustomAuthProvider.Guest)
{
Debugger.Break();//breaks as expected
}
if (await _blazorInjectedAuthProvider.GetAuthenticationStateAsync() == CustomAuthProvider.Guest)
{
Debugger.Break();//breaks as expected
}
}
}
```
I can supply a repro if needed. Using .NET 7.
### Expected Behavior
I would expect every single Debugger.Break() to trigger since the GetAuthenticationState Task, when awaited, should always return the static guest authentication state object. Why do the two different DI registrations have different side-effects/outcomes? I am expecting all instances of `Task` to be referencing `GuestAuthenticationState` but it's not the case for Blazor injected services and Cascading Authentication values.
Does Blazor use a different implementation for providing `Task` as a cascading value than the one registered via DI?
### Steps To Reproduce
_No response_
### Exceptions (if any)
_No response_
### .NET Version
7
### Anything else?
_No response_
Contributor guide
Assessment
This issue has not been assessed yet.