dotnet / dotnet/aspnetcore

Access `AuthenticationStateProvider` in outgoing request middleware

Open
#52,379 17 comments 17 reactions 0 assignees View on GitHub
area-blazor bug Pillar: Technical Debt
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

I try to access AuthenticationStateProvider from a DelegatingHandler which intercepts requests from an HttpClient, following the official documentation guidelines:

- **Access AuthenticationStateProvider in outgoing request middleware** [Click here](https://learn.microsoft.com/en-us/aspnet/core/blazor/security/server/additional-scenarios?view=aspnetcore-8.0#access-authenticationstateprovider-in-outgoing-request-middleware)
- **Access server-side Blazor services from a different DI scope** [Click here](https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/dependency-injection?view=aspnetcore-8.0#access-server-side-blazor-services-from-a-different-di-scope)

But I get errors when implementing it in a completely new Blazor project (Server Side) in .NET 8 SDK.

### Expected Behavior

I am looking to access the AuthenticationStateProvider from the DelegateHandler.

### Steps To Reproduce

**Program.cs**
```csharp
using Microsoft.AspNetCore.Components.Server.Circuits;
using Minerva.Components;
using Minerva.Services.Security;
using Minerva.Services.Util;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();

builder.Configuration.AddEnvironmentVariables();
var configuration = builder.Configuration;

// test
builder.Services.AddScoped();
builder.Services.AddScoped();

services.AddTransient();
services.AddHttpClient("Backend.Secured").AddHttpMessageHandler();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();
app.UseAntiforgery();

app.MapRazorComponents()
.AddInteractiveServerRenderMode();

app.Run();
```

**SecurityServiceAccessor.cs**
```csharp
namespace Minerva.Services.Util;

public class SecurityServiceAccessor
{
private readonly AsyncLocal _services = new();

public IServiceProvider? Services
{
get => _services.Value;
set => _services.Value = value;
}
}
```

**SecurityCircuitHandler.cs**
```csharp
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server.Circuits;

namespace Minerva.Services.Util;

public class SecurityCircuitHandler : CircuitHandler
{
private readonly IServiceProvider _serviceProvider;
private readonly SecurityServiceAccessor _serviceAccessor;

public SecurityCircuitHandler(IServiceProvider serviceProvider, SecurityServiceAccessor serviceAccessor)
{
_serviceProvider = serviceProvider;
_serviceAccessor = serviceAccessor;
}

public override Task OnCircuitOpenedAsync(Circuit circuit, CancellationToken cancellationToken)
{
Console.WriteLine("Circuit Opened - " + DateTimeOffset.UtcNow);

_serviceAccessor.Services = _serviceProvider;
Console.WriteLine("Service Provider Assigned - null: " +
(_serviceAccessor.Services.GetService() == null));

return base.OnCircuitOpenedAsync(circuit, cancellationToken);
}

public override Task OnConnectionUpAsync(Circuit circuit, CancellationToken cancellationToken)
{
Console.WriteLine("Connection Up - " + DateTimeOffset.UtcNow);

_serviceAccessor.Services = _serviceProvider;
Console.WriteLine("Service Provider Assigned - null: " +
(_serviceAccessor.Services.GetService() == null));

return base.OnConnectionUpAsync(circuit, cancellationToken);
}

public override Func CreateInboundActivityHandler(
Func next)
{
Console.WriteLine("Circuit Activity - " + DateTimeOffset.UtcNow);

_serviceAccessor.Services = _serviceProvider;
Console.WriteLine("Service Provider Assigned - null: " +
(_serviceAccessor.Services.GetService() == null));

return base.CreateInboundActivityHandler(next);
}

public override Task OnConnectionDownAsync(Circuit circuit, CancellationToken cancellationToken)
{
Console.WriteLine("Connection Down - " + DateTimeOffset.UtcNow);

_serviceAccessor.Services = default;

return base.OnConnectionDownAsync(circuit, cancellationToken);
}

public override Task OnCircuitClosedAsync(Circuit circuit, CancellationToken cancellationToken)
{
Console.WriteLine("Circuit Closed - " + DateTimeOffset.UtcNow);

_serviceAccessor.Services = default;

return base.OnCircuitClosedAsync(circuit, cancellationToken);
}
}
```

**SecurityHandler.cs**
```csharp
using Microsoft.AspNetCore.Components.Authorization;
using Minerva.Services.Util;

namespace Minerva.Services.Security;

public class SecurityHandler : DelegatingHandler
{
private readonly SecurityServiceAccessor _serviceAccessor;

public SecurityHandler(SecurityServiceAccessor serviceAccessor)
{
_serviceAccessor = serviceAccessor;
}

protected override async Task SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
Console.WriteLine("Handling...");

Console.WriteLine("Service Provider Reading - null: " +
(_serviceAccessor.Services.GetService() == null));

Console.WriteLine("Sending async...");
return await base.SendAsync(request, cancellationToken);
}
}
```

**Home.razor**
```csharp
@page "/"
@attribute [Authorize]
@rendermode InteractiveServer
@inject IHttpClientFactory ClientFactory

Home

Hello, world!

Welcome to your new app.

@code{

private async Task Callback()
{
var httpClient = ClientFactory.CreateClient("Backend.Secured");
var result = await httpClient.GetAsync("https://catfact.ninja/breeds");
Console.WriteLine(result.StatusCode);
}

}
```

### Exceptions (if any)

```console
warn: Microsoft.AspNetCore.Components.Server.Circuits.RemoteRenderer[100]
Unhandled exception rendering component: Value cannot be null. (Parameter 'provider')
System.ArgumentNullException: Value cannot be null. (Parameter 'provider')
at System.ThrowHelper.Throw(String paramName)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetService[T](IServiceProvider provider)
at Minerva.Services.Security.SecurityHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) in C:\Users\ggomarighetti\source\repos\minerva-front\Minerva\Services\Security\SecurityHandler.cs:l
ine 29
at Microsoft.Extensions.Http.Logging.LoggingScopeHttpMessageHandler.g__Core|5_0(HttpRequestMessage request, Boolean useAsync, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, Cance
llationToken originalCancellationToken)
at Minerva.Components.Pages.Home.Callback() in C:\Users\ggomarighetti\source\repos\minerva-front\Minerva\Components\Pages\Home.razor:line 20
at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)
fail: Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost[111]
Unhandled exception in circuit 'rKmKcYZ93K8cqm9heF7tQg9y-yxOHUqOi_ZKjRX8FC8'.
System.ArgumentNullException: Value cannot be null. (Parameter 'provider')
at System.ThrowHelper.Throw(String paramName)
er.cs:line 56
at Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost.OnConnectionDownAsync(CancellationToken cancellationToken)
```

### .NET Version

8.0.100

### Anything else?

SDK DE .NET:
Version: 8.0.100
Commit: 57efcf1350
Workload version: 8.0.100-manifests.6a1e483a

Entorno de tiempo de ejecución:
OS Name: Windows
OS Version: 10.0.19045
OS Platform: Windows
RID: win-x64
Base Path: C:\Program Files\dotnet\sdk\8.0.100\

Cargas de trabajo de .NET instaladas:
Workload version: 8.0.100-manifests.6a1e483a
[ios]
Origen de la instalación: VS 17.8.34316.72
Versión del manifiesto: 17.0.8478/8.0.100
Ruta de acceso del manifiesto: C:\Program Files\dotnet\sdk-manifests\8.0.100\microsoft.net.sdk.ios\17.0.8478\WorkloadManifest.json
Tipo de instalación: Msi

[maui-windows]
Origen de la instalación: VS 17.8.34316.72
Versión del manifiesto: 8.0.3/8.0.100
Ruta de acceso del manifiesto: C:\Program Files\dotnet\sdk-manifests\8.0.100\microsoft.net.sdk.maui\8.0.3\WorkloadManifest.json
Tipo de instalación: Msi

[android]
Origen de la instalación: VS 17.8.34316.72
Versión del manifiesto: 34.0.43/8.0.100
Ruta de acceso del manifiesto: C:\Program Files\dotnet\sdk-manifests\8.0.100\microsoft.net.sdk.android\34.0.43\WorkloadManifest.json
Tipo de instalación: Msi

[maccatalyst]
Origen de la instalación: VS 17.8.34316.72
Versión del manifiesto: 17.0.8478/8.0.100
Ruta de acceso del manifiesto: C:\Program Files\dotnet\sdk-manifests\8.0.100\microsoft.net.sdk.maccatalyst\17.0.8478\WorkloadManifest.json
Tipo de instalación: Msi

Host:
Version: 8.0.0
Architecture: x64
Commit: 5535e31a71

.NET SDKs installed:
6.0.403 [C:\Program Files\dotnet\sdk]
8.0.100 [C:\Program Files\dotnet\sdk]

.NET runtimes installed:
Microsoft.AspNetCore.App 3.1.32 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 6.0.11 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 6.0.25 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 7.0.14 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 8.0.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 3.1.32 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.11 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.25 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 7.0.14 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 8.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 3.1.32 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 6.0.11 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 6.0.25 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 7.0.14 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 8.0.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

Other architectures found:
x86 [C:\Program Files (x86)\dotnet]
registered at [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\x86\InstallLocation]

Environment variables:
Not set

global.json file:
C:\Users\ggomarighetti\source\repos\minerva-front\global.json

Learn more:
https://aka.ms/dotnet/info

Download .NET:
https://aka.ms/dotnet/download

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.