Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions aspnetcore/blazor/call-web-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,18 @@ The `BlazorWebAppCallWebApi` [sample app](#sample-apps) demonstrates calling a w

:::moniker-end

:::moniker range=">= aspnetcore-8.0"

## Accessing services outside of the `HttpClient`'s scope

<xref:System.Net.Http.IHttpClientFactory> creates <xref:System.Net.Http.DelegatingHandler> instances in a separate dependency injection (DI) scope from the app. If you inject a scoped service into a derived <xref:System.Net.Http.DelegatingHandler> type, the handler doesn't have access to the service from the Blazor circuit.

For an example of how to access a service in outgoing request middleware using either an application scope handler or a circuit activity handler, see <xref:blazor/security/additional-scenarios#access-authenticationstateprovider-in-outgoing-request-middleware>.

For more information on <xref:System.Net.Http.DelegatingHandler> instances, see <xref:fundamentals/http-requests#outgoing-request-middleware>.

:::moniker-end

## Disposal of `HttpRequestMessage`, `HttpResponseMessage`, and `HttpClient`

An <xref:System.Net.Http.HttpRequestMessage> without a body doesn't require explicit disposal. However, you can dispose of it with either of the following patterns:
Expand Down
4 changes: 2 additions & 2 deletions aspnetcore/blazor/fundamentals/dependency-injection.md
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,7 @@ For more information, see <xref:blazor/blazor-ef-core>.

:::moniker range=">= aspnetcore-8.0"

[Circuit activity handlers](xref:blazor/fundamentals/signalr#monitor-server-side-circuit-activity) provide an approach for accessing scoped Blazor services from other non-Blazor dependency injection (DI) scopes, such as scopes created using <xref:System.Net.Http.IHttpClientFactory>.
[Circuit activity handlers](xref:blazor/fundamentals/signalr#monitor-server-side-circuit-activity) provide an approach for accessing scoped Blazor services from other non-Blazor dependency injection (DI) scopes.

Prior to the release of ASP.NET Core in .NET 8, accessing circuit-scoped services from other dependency injection scopes required using a custom base component type. With circuit activity handlers, a custom base component type isn't required, as the following example demonstrates:

Expand Down Expand Up @@ -637,7 +637,7 @@ builder.Services.AddCircuitServicesAccessor();

Access the circuit-scoped services by injecting the `CircuitServicesAccessor` where it's needed.

For an example that shows how to access the <xref:Microsoft.AspNetCore.Components.Authorization.AuthenticationStateProvider> from a <xref:System.Net.Http.DelegatingHandler> set up using <xref:System.Net.Http.IHttpClientFactory>, see <xref:blazor/security/additional-scenarios#access-authenticationstateprovider-in-outgoing-request-middleware>.
For an example that shows how to access the <xref:Microsoft.AspNetCore.Components.Authorization.AuthenticationStateProvider> from a <xref:System.Net.Http.DelegatingHandler> set up using <xref:System.Net.Http.IHttpClientFactory>, see the circuit activity handler approach in <xref:blazor/security/additional-scenarios#access-authenticationstateprovider-in-outgoing-request-middleware>.

:::moniker-end

Expand Down
161 changes: 158 additions & 3 deletions aspnetcore/blazor/security/additional-scenarios.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ This article explains how to configure server-side Blazor for additional securit

If you merely want to use access tokens to make web API calls from a Blazor Web App with a [named HTTP client](xref:blazor/call-web-api#named-httpclient-with-ihttpclientfactory), see the [Use a token handler for web API calls](#use-a-token-handler-for-web-api-calls) section, which explains how to use a <xref:System.Net.Http.DelegatingHandler> implementation to attach a user's access token to outgoing requests. The following guidance in this section is for developers who need access tokens, refresh tokens, and other authentication properties server-side for other purposes.

> [!NOTE]
> For more information on <xref:System.Net.Http.DelegatingHandler> instances, see <xref:fundamentals/http-requests#outgoing-request-middleware>.

To save tokens and other authentication properties for server-side use in Blazor Web Apps, we recommend using [`IHttpContextAccessor`/`HttpContext`](xref:blazor/components/httpcontext) (<xref:Microsoft.AspNetCore.Http.IHttpContextAccessor>, <xref:Microsoft.AspNetCore.Http.HttpContext>). Reading tokens from <xref:Microsoft.AspNetCore.Http.HttpContext>, including as a [cascading parameter](xref:Microsoft.AspNetCore.Components.CascadingParameterAttribute), using <xref:Microsoft.AspNetCore.Http.IHttpContextAccessor> is supported for obtaining tokens for use during interactive server rendering if the tokens are obtained during static server-side rendering (static SSR) or prerendering. However, tokens aren't updated if the user authenticates after the circuit is established, since the <xref:Microsoft.AspNetCore.Http.HttpContext> is captured at the start of the SignalR connection. Also, the use of <xref:System.Threading.AsyncLocal%601> by <xref:Microsoft.AspNetCore.Http.IHttpContextAccessor> means that you must be careful not to lose the execution context before reading the <xref:Microsoft.AspNetCore.Http.HttpContext>. For more information, see <xref:blazor/components/httpcontext>.

In a service class, obtain access to the members of the namespace <xref:Microsoft.AspNetCore.Authentication?displayProperty=fullName> to surface the <xref:Microsoft.AspNetCore.Authentication.AuthenticationHttpContextExtensions.GetTokenAsync%2A> method on <xref:Microsoft.AspNetCore.Http.HttpContext>. An alternative approach, which is commented out in the following example, is to call <xref:Microsoft.AspNetCore.Authentication.AuthenticationHttpContextExtensions.AuthenticateAsync%2A> on <xref:Microsoft.AspNetCore.Http.HttpContext>. For the returned <xref:Microsoft.AspNetCore.Authentication.AuthenticateResult.Properties%2A?displayProperty=nameWithType>, call <xref:Microsoft.AspNetCore.Authentication.AuthenticationTokenExtensions.GetTokenValue%2A>.
Expand Down Expand Up @@ -848,15 +851,167 @@ app.UseMiddleware<UserServiceMiddleware>();

## Access `AuthenticationStateProvider` in outgoing request middleware

The <xref:Microsoft.AspNetCore.Components.Authorization.AuthenticationStateProvider> from a <xref:System.Net.Http.DelegatingHandler> for <xref:System.Net.Http.HttpClient> created with <xref:System.Net.Http.IHttpClientFactory> can be accessed in outgoing request middleware using a [circuit activity handler](xref:blazor/fundamentals/signalr#monitor-circuit-activity-blazor-server).
<xref:System.Net.Http.IHttpClientFactory> creates <xref:System.Net.Http.DelegatingHandler> instances in a separate dependency injection (DI) scope from the app. If you inject <xref:Microsoft.AspNetCore.Components.Authorization.AuthenticationStateProvider> into a derived <xref:System.Net.Http.DelegatingHandler> type, the handler doesn't have access to the current user's authentication state from the Blazor circuit.

Use either of the following approaches to address this scenario:

* [Application scope handler](#application-scope-handler-recommended) (*Recommended*)
* [Circuit activity handler](#circuit-activity-handler)

> [!NOTE]
> For general guidance on defining delegating handlers for HTTP requests by <xref:System.Net.Http.HttpClient> instances created using <xref:System.Net.Http.IHttpClientFactory> in ASP.NET Core apps, see the following sections of <xref:fundamentals/http-requests>:
> For general guidance on defining delegating handlers for HTTP requests by <xref:System.Net.Http.HttpClient> instances created using <xref:System.Net.Http.IHttpClientFactory>, see the following sections of <xref:fundamentals/http-requests>:
>
> * [Outgoing request middleware](xref:fundamentals/http-requests#outgoing-request-middleware)
> * [Use DI in outgoing request middleware](xref:fundamentals/http-requests#use-di-in-outgoing-request-middleware)

The following example uses <xref:Microsoft.AspNetCore.Components.Authorization.AuthenticationStateProvider> to attach a custom user name header for authenticated users to outgoing requests.
The examples in the following subsections attach a custom user name header for authenticated users to outgoing requests.

### Application scope handler (*Recommended*)

The approach in this section uses a [keyed service](xref:fundamentals/dependency-injection#keyed-services) to register a custom <xref:System.Net.Http.HttpClient> that wraps the base client with an application scope handler resolved from the current application scope to access <xref:Microsoft.AspNetCore.Components.Authorization.AuthenticationStateProvider>.

Overview of the approach:

* Base client configuration: <xref:Microsoft.Extensions.DependencyInjection.HttpClientFactoryServiceCollectionExtensions.AddHttpClient%2A> is called to register a [named client](xref:blazor/call-web-api#named-httpclient-with-ihttpclientfactory) with <xref:System.Net.Http.IHttpClientFactory>.
* Keyed registration: A custom `AddApplicationScopeHandler` extension method registers a keyed <xref:System.Net.Http.HttpClient> with the same client name.
* Scope-aware handler: The application scope handler is resolved from the current scope, giving it access to <xref:Microsoft.AspNetCore.Components.Authorization.AuthenticationStateProvider>.
* Handler caching: The application scope handler uses <xref:System.Net.Http.IHttpMessageHandlerFactory> to get the cached <xref:System.Net.Http.HttpMessageHandler>, which preserves connection pooling.
* Configuration reuse: The application scope handler applies the same <xref:Microsoft.Extensions.Http.HttpClientFactoryOptions> configuration to its <xref:System.Net.Http.HttpClient> as the base client.

Create the following methods and classes:

* `AddApplicationScopeHandler`: An extension method to add the application scope handler and a keyed <xref:System.Net.Http.HttpClient> service to the DI container.
* `ApplicationScopeHandler`: The application scope handler class.
* `AuthenticationStateHandler`: A <xref:System.Net.Http.DelegatingHandler> that attaches a custom user name header for authenticated users to outgoing requests.

`Services/ApplicationScopeHttpClientExtensions.cs`:

```csharp
using System.Security.Claims;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.Http;
using Microsoft.Extensions.Options;

namespace BlazorSample.Services;

public static class ApplicationScopeHttpClientExtensions
{
public static readonly HttpRequestOptionsKey<IServiceProvider> ScopeKey =
new("ApplicationScope");

public static IHttpClientBuilder AddApplicationScopeHandler(
this IHttpClientBuilder builder)
{
var name = builder.Name;

builder.Services.AddTransient<ApplicationScopeHandler>();

builder.Services.AddKeyedScoped<HttpClient>(name, (sp, key) =>
{
var handler = sp.GetRequiredService<ApplicationScopeHandler>();
handler.InnerHandler =
sp.GetRequiredService<IHttpMessageHandlerFactory>()
.CreateHandler(name);

var client = new HttpClient(handler, disposeHandler: false);

var options =
sp.GetRequiredService<IOptionsMonitor<HttpClientFactoryOptions>>()
.Get(name);

foreach (var action in options.HttpClientActions)
{
action(client);
}

return client;
});

return builder;
}
}

public class ApplicationScopeHandler(IServiceProvider serviceProvider)
: DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
request.Options.Set(ApplicationScopeHttpClientExtensions.ScopeKey,
serviceProvider);
return base.SendAsync(request, cancellationToken);
}
}

public class AuthenticationStateHandler : DelegatingHandler
{
private ClaimsPrincipal? user;

protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (user is null)
{
if (request.Options.TryGetValue(
ApplicationScopeHttpClientExtensions.ScopeKey, out var sp))
{
var authStateProvider = sp.GetService<AuthenticationStateProvider>();

if (authStateProvider is not null)
{
user = (await authStateProvider.GetAuthenticationStateAsync())
.User;
}
}
}

if (user?.Identity?.IsAuthenticated)
{
request.Headers.TryAddWithoutValidation("X-USER-IDENTITY-NAME",
user.Identity.Name);
}

return await base.SendAsync(request, cancellationToken);
}
}
```

The `AuthenticationStateHandler` in the preceding example caches the user for the lifetime of the <xref:System.Net.Http.DelegatingHandler>. To fetch the user's current authentication state for each request, remove the `null` conditional check on the user.

Register the named client in the `Program` file, calling `AddApplicationScopeHandler` to add the application scope handler:

```csharp
builder.Services.AddHttpClient("ExternalApi", client =>
{
client.BaseAddress = new Uri("{REQUEST URI}");
})
.AddApplicationScopeHandler()
.AddHttpMessageHandler<AuthenticationStateHandler>();
```

The `{REQUEST URI}` placeholder in the preceding example is the request URI (localhost example: `http://localhost:5209`).

Inject the client into components using the keyed service:

```razor
@using Microsoft.Extensions.DependencyInjection

@code {
[Inject(Key = "ExternalApi")]
public HttpClient Http { get; set; } = default!;

private async Task CallApiAsync()
{
var response = await Http.GetAsync("/api/endpoint");
}
}
```

### Circuit activity handler

The approach in this section uses a [circuit activity handler](xref:blazor/fundamentals/signalr#monitor-circuit-activity-blazor-server) to access the <xref:Microsoft.AspNetCore.Components.Authorization.AuthenticationStateProvider>, which is an alternative to the recommended [application scope handler approach](#application-scope-handler-recommended) in the preceding section.

First, implement the `CircuitServicesAccessor` class in the following section of the Blazor dependency injection (DI) article:

Expand Down
2 changes: 1 addition & 1 deletion aspnetcore/blazor/security/blazor-web-app-with-oidc.md
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ Sample solution features:

* The app securely calls a web API for weather data:

* When rendering the `Weather` component on the server, the component uses the `ServerWeatherForecaster` on the server to obtain weather data from the web API in the `MinimalApiJwt` project using a <xref:System.Net.Http.DelegatingHandler> (`TokenHandler`) that attaches the access token from the <xref:Microsoft.AspNetCore.Http.HttpContext> to the request.
* When rendering the `Weather` component on the server, the component uses the `ServerWeatherForecaster` on the server to obtain weather data from the web API in the `MinimalApiJwt` project using a <xref:System.Net.Http.DelegatingHandler> (`TokenHandler`) that attaches the access token from the <xref:Microsoft.AspNetCore.Http.HttpContext> to the request. For more information on <xref:System.Net.Http.DelegatingHandler> instances, see <xref:fundamentals/http-requests#outgoing-request-middleware>.
* When the component is rendered on the client, the component uses the `ClientWeatherForecaster` service implementation, which uses a preconfigured <xref:System.Net.Http.HttpClient> (in the client project's `Program` file) to make the web API call from the server project's `ServerWeatherForecaster`.

:::moniker range=">= aspnetcore-9.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ For convenience, the framework provides the <xref:Microsoft.AspNetCore.Component
In the following example:

* <xref:Microsoft.Extensions.DependencyInjection.HttpClientFactoryServiceCollectionExtensions.AddHttpClient%2A> adds <xref:System.Net.Http.IHttpClientFactory> and related services to the service collection and configures a named <xref:System.Net.Http.HttpClient> (`WebAPI`). <xref:System.Net.Http.HttpClient.BaseAddress?displayProperty=nameWithType> is the base address of the resource URI when sending requests. <xref:System.Net.Http.IHttpClientFactory> is provided by the [`Microsoft.Extensions.Http`](https://www.nuget.org/packages/Microsoft.Extensions.Http) NuGet package.
* <xref:Microsoft.AspNetCore.Components.WebAssembly.Authentication.BaseAddressAuthorizationMessageHandler> is the <xref:System.Net.Http.DelegatingHandler> used to process access tokens. Access tokens are only added when the request URI is within the app's base URI.
* <xref:Microsoft.AspNetCore.Components.WebAssembly.Authentication.BaseAddressAuthorizationMessageHandler> is the <xref:System.Net.Http.DelegatingHandler> used to process access tokens. Access tokens are only added when the request URI is within the app's base URI. For more information on <xref:System.Net.Http.DelegatingHandler> instances, see <xref:fundamentals/http-requests#outgoing-request-middleware>.

* <xref:System.Net.Http.IHttpClientFactory.CreateClient%2A?displayProperty=nameWithType> creates and configures an <xref:System.Net.Http.HttpClient> instance for outgoing requests using the configuration that corresponds to the named <xref:System.Net.Http.HttpClient> (`WebAPI`).

In the following example, <xref:Microsoft.Extensions.DependencyInjection.HttpClientFactoryServiceCollectionExtensions.AddHttpClient%2A?displayProperty=nameWithType> is an extension in <xref:Microsoft.Extensions.Http?displayProperty=fullName>. Add the package to an app that doesn't already reference it.
Expand Down
1 change: 1 addition & 0 deletions aspnetcore/blazor/security/webassembly/graph-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -938,6 +938,7 @@ In the preceding example, the `GraphAuthorizationMessageHandler` <xref:System.Ne

* [Utility base component classes to manage a DI scope](xref:blazor/fundamentals/dependency-injection#utility-base-component-classes-to-manage-a-di-scope)
* [Detect client-side transient disposables](xref:blazor/fundamentals/dependency-injection#detect-client-side-transient-disposables)
* [`DelegatingHandler` instances](xref:fundamentals/http-requests#outgoing-request-middleware)

A trailing slash (`/`) on the base address is required. In the preceding code, the third argument to `string.Join` is `string.Empty` to ensure the trailing slash is present: `https://graph.microsoft.com/v1.0/`.

Expand Down
6 changes: 3 additions & 3 deletions aspnetcore/fundamentals/dependency-injection.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,13 @@ The term *keyed services* refers to a mechanism for registering and retrieving D

:::code language="csharp" source="~/../AspNetCore.Docs.Samples/samples/KeyedServices9/Program.cs" highlight="6,7,12-14,39,47" id="snippet_1":::

#### Keyed services in Middleware
#### Keyed services in middleware

Middleware supports Keyed services in both the constructor and the `Invoke`/`InvokeAsync` method:
Middleware supports keyed services in both the constructor and the `Invoke`/`InvokeAsync` method:

:::code language="csharp" source="~/../AspNetCore.Docs.Samples/samples/KeyedServices9/Program.cs" id="snippet_2":::

For more information on creating Middleware, see <xref:fundamentals/middleware/write>
For more information on creating Middleware, see <xref:fundamentals/middleware/write>.

## Constructor injection behavior

Expand Down