dotnet / dotnet/aspnetcore

Custom AuthenticationStateProvider Unexpected Task<AuthenticationState> / Odd behavior

Open
#45,517 4 comments 0 reactions 0 assignees View on GitHub
area-blazor External question
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.