次の方法で共有


コア サーバー側と Blazor Web App 追加のセキュリティ シナリオを ASP.NET する

これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の .NET 9 バージョンを参照してください。

警告

このバージョンの ASP.NET Core はサポート対象から除外されました。 詳細については、 .NET および .NET Core サポート ポリシーを参照してください。 現在のリリースについては、この記事の .NET 9 バージョンを参照してください。

重要

この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft はここに示されている情報について、明示か黙示かを問わず、一切保証しません。

現在のリリースについては、この記事の .NET 9 バージョンを参照してください。

この記事では、Blazor アプリにトークンを渡す方法など、追加のセキュリティ シナリオのためにサーバー側の Blazor を構成する方法について説明します。

この記事のコード例では、null 許容参照型 (NRT) と .NET コンパイラの null 状態スタティック分析を採用しています。これは、.NET 6 以降の ASP.NET Core でサポートされています。 .NET 5 以前を対象とする場合は、記事の例の?string?TodoItem[]?、およびWeatherForecast[]?型から null 型の指定 (IEnumerable<GitHubBranch>?) を削除します。

サーバー側の Blazor アプリにトークンを渡す

このセクションは、 Blazor Web Appに適用されます。 Blazor Serverについては、この記事セクションの .NET 7 バージョンを参照してください。

アクセス トークンを使用してBlazor Web Appを使用してから Web API 呼び出しを行うだけの場合は、「Web API 呼び出しにトークン ハンドラーを使用する」セクションを参照してください。このセクションでは、DelegatingHandler実装を使用してユーザーのアクセス トークンを送信要求にアタッチする方法について説明しています。 このセクションの次のガイダンスは、他の目的でサーバー側でアクセス トークン、更新トークン、およびその他の認証プロパティを必要とする開発者を対象としています。

Blazor Web Appでサーバー側で使用するためにトークンやその他の認証プロパティを保存するには、IHttpContextAccessor/HttpContext (IHttpContextAccessorHttpContext) を使用することをお勧めします。 静的サーバーサイドレンダリング(静的SSR)またはプリレンダリング中にトークンが取得されている場合、HttpContextを使用してからトークンを読み取り、IHttpContextAccessorとして取得することが、対話型サーバーレンダリング中に使用するためにサポートされています。 ただし、HttpContext接続の開始時にSignalRがキャプチャされるため、回線の確立後にユーザーが認証した場合、トークンは更新されません。 また、AsyncLocal<T>によるIHttpContextAccessorの使用は、HttpContextを読む前に実行コンテキストを失わないように注意する必要があることを意味します。 詳細については、core Blazor アプリの IHttpContextAccessor/HttpContext ASP.NETを参照してください。

サービス クラスで、名前空間Microsoft.AspNetCore.Authenticationのメンバーへのアクセスを取得して、GetTokenAsyncHttpContext メソッドを表示します。 次の例でコメントアウトされている別の方法は、AuthenticateAsyncHttpContextを呼び出す方法です。 返された AuthenticateResult.Propertiesの場合は、 GetTokenValueを呼び出します。

using Microsoft.AspNetCore.Authentication;

public class AuthenticationProcessor(IHttpContextAccessor httpContextAccessor)
{
    public async Task<string?> GetAccessToken()
    {
        if (httpContextAccessor.HttpContext is null)
        {
            throw new Exception("HttpContext not available");
        }

        // Approach 1: Call 'GetTokenAsync'
        var accessToken = await httpContextAccessor.HttpContext
            .GetTokenAsync("access_token");

        // Approach 2: Authenticate the user and call 'GetTokenValue'
        /*
        var authResult = await httpContextAccessor.HttpContext.AuthenticateAsync();
        var accessToken = authResult?.Properties?.GetTokenValue("access_token");
        */

        return accessToken;
    }
}

サービスは、サーバー プロジェクトの Program ファイルに登録されます。

builder.Services.AddScoped<AuthenticationProcessor>();

サーバー側のサービスにAuthenticationProcessorを挿入できます。たとえば、事前構成済みのDelegatingHandlerのためのHttpClientに挿入することができます。 次の例は、デモンストレーションのみを目的としています。または、 AuthenticationProcessor サービスで特別な処理を実行する必要がある場合は、 IHttpContextAccessor を挿入し、外部 Web API を呼び出すためのトークンを直接取得できます ( IHttpContextAccessor を直接使用して Web API を呼び出す方法の詳細については、「 Web API 呼び出しにトークン ハンドラーを使用 する」セクションを参照してください)。

using System.Net.Http.Headers;

public class TokenHandler(AuthenticationProcessor authProcessor) : 
    DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var accessToken = authProcessor.GetAccessToken();

        request.Headers.Authorization =
            new AuthenticationHeaderValue("Bearer", accessToken);

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

トークン ハンドラーが登録され、 Program ファイル内の名前付き HTTP クライアントの委任ハンドラーとして機能します。

builder.Services.AddHttpContextAccessor();

builder.Services.AddScoped<TokenHandler>();

builder.Services.AddHttpClient("ExternalApi",
      client => client.BaseAddress = new Uri(builder.Configuration["ExternalApiUri"] ?? 
          throw new Exception("Missing base address!")))
      .AddHttpMessageHandler<TokenHandler>();

注意事項

対話型自動レンダリングを採用し、クライアントまたはクライアント側のサービスによってレンダリングされるコンポーネントなど、トークンがクライアント ( .Client プロジェクト) によって送信および処理されないようにします。 クライアントが常にサーバー (プロジェクト) を呼び出して、トークンを使用して要求を処理するようにします。 トークンやその他の認証データがサーバーから離れることはありません。

対話型の自動コンポーネントについては、「 ASP.NET Core Blazor 認証と承認」を参照してください。これは、アクセス トークンやその他の認証プロパティをサーバーに残す方法を示しています。 また、同様の呼び出し構造を採用するバックエンド フロントエンド (BFF) パターンの採用を検討してください。これは、OIDC プロバイダー向けの OpenID Connect (OIDC) を使用した ASP.NET Core Blazor Web Appのセキュリティ保護と、Microsoft Blazor Web App Web 用 Identityのセキュリティ保護に関するページで説明されています。

Web API 呼び出しにトークン ハンドラーを使用する

次のアプローチは、ユーザーのアクセス トークンを送信要求にアタッチすることを目的としています。特に、外部 Web API アプリへの Web API 呼び出しを行います。 このアプローチは、グローバル Interactive Server レンダリングを採用する Blazor Web App に対して示されていますが、グローバル対話型自動レンダリング モードを採用する Blazor Web Appにも同じ一般的なアプローチが適用されます。 留意すべき重要な概念は、HttpContextを使用してIHttpContextAccessorにアクセスすることは、サーバー上でのみ実行されるということです。

このセクションのガイダンスのデモについては、BlazorWebAppOidcのサンプル アプリ (.NET 8 以降) のBlazorWebAppOidcServerとBlazorを参照してください。 サンプルでは、Entra 固有のパッケージを使用せずに、Microsoft Entra でグローバル対話型レンダリング モードと OIDC 認証を採用しています。 サンプルでは、JWT アクセス トークンを渡してセキュリティで保護された Web API を呼び出す方法を示します。

Microsoft Entra IDの Microsoft Identity Web パッケージを使用する Microsoft ID プラットフォームには、トークンの自動管理と更新を使用してBlazor Web Appから Web API を呼び出す API が用意されています。 詳細については、Blazor Web Appサンプル GitHub リポジトリBlazorWebAppEntraする」とBlazorWebAppEntraBffおよびBlazorサンプル アプリ (.NET 9 以降) を参照してください。

ユーザーのアクセス トークンを送信要求にアタッチするサブクラス DelegatingHandler 。 トークン ハンドラーはサーバー上でのみ実行されるため、 HttpContext を使用しても安全です。

TokenHandler.cs:

using System.Net.Http.Headers;
using Microsoft.AspNetCore.Authentication;

public class TokenHandler(IHttpContextAccessor httpContextAccessor) : 
    DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if (httpContextAccessor.HttpContext is null)
        {
            throw new Exception("HttpContext not available");
        }

        var accessToken = await httpContextAccessor.HttpContext.GetTokenAsync("access_token");

        if (accessToken is null)
        {
            throw new Exception("No access token");
        }

        request.Headers.Authorization =
            new AuthenticationHeaderValue("Bearer", accessToken);

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

AuthenticationStateProviderからDelegatingHandlerを取得する方法については、「送信リクエストミドルウェア内でのAuthenticationStateProviderへのアクセス」セクションを参照してください。

プロジェクトのProgram ファイルでは、トークン ハンドラー (TokenHandler) がスコープ付きサービスとして登録され、メッセージ ハンドラーとして指定されます。

次の例では、 {HTTP CLIENT NAME} プレースホルダーは HttpClientの名前であり、 {BASE ADDRESS} プレースホルダーは Web API のベース アドレス URI です。 AddHttpContextAccessorの詳細については、「ASP.NET Core Blazor アプリの IHttpContextAccessor/HttpContext」を参照してください。

Program.cs:

builder.Services.AddHttpContextAccessor();

builder.Services.AddScoped<TokenHandler>();

builder.Services.AddHttpClient("{HTTP CLIENT NAME}",
      client => client.BaseAddress = new Uri("{BASE ADDRESS}"))
      .AddHttpMessageHandler<TokenHandler>();

例:

builder.Services.AddScoped<TokenHandler>();

builder.Services.AddHttpClient("ExternalApi",
      client => client.BaseAddress = new Uri("https://localhost:7277"))
      .AddHttpMessageHandler<TokenHandler>();

を使用してbuilder.Configuration["{CONFIGURATION KEY}"]から HTTP クライアントのベース アドレスを指定できます。ここで、{CONFIGURATION KEY}プレースホルダーは構成キーです。

new Uri(builder.Configuration["ExternalApiUri"] ?? throw new IOException("No URI!"))

appsettings.jsonで、ExternalApiUriを指定します。 次の例では、外部 Web API の localhost アドレスにhttps://localhost:7277として値を設定します。

"ExternalApiUri": "https://localhost:7277"

この時点で、コンポーネントによって作成された HttpClient は、セキュリティで保護された Web API 要求を行うことができます。 次の例では、 {REQUEST URI} は相対要求 URI であり、 {HTTP CLIENT NAME} プレースホルダーは HttpClientの名前です。

using var request = new HttpRequestMessage(HttpMethod.Get, "{REQUEST URI}");
var client = ClientFactory.CreateClient("{HTTP CLIENT NAME}");
using var response = await client.SendAsync(request);

例:

using var request = new HttpRequestMessage(HttpMethod.Get, "/weather-forecast");
var client = ClientFactory.CreateClient("ExternalApi");
using var response = await client.SendAsync(request);

Blazorの追加機能は計画されており、送信リクエストミドルウェア (AuthenticationStateProvider #52379) の中で Access dotnet/aspnetcoreによって追跡されています。 対話型サーバー モード (dotnet/aspnetcore #52390) での HttpClient へのアクセス トークンの提供 に関する問題は、高度なユース ケースに役立つディスカッションと潜在的な回避策を含むクローズされた問題です。

サーバー側の Razor アプリの Blazor コンポーネントの外部で使用できるトークンは、このセクションで説明する方法でコンポーネントに渡すことができます。 このセクションの例での焦点は、アクセス トークン、更新トークン、リクエスト フォージェリ防止 (XSRF) トークンを Blazor アプリに渡すことですが、このアプローチは他の HTTP コンテキスト状態に対しても有効です。

Razor コンポーネントに XSRF トークンを渡す処理は、コンポーネントが Identity や検証を必要とするその他のエンドポイントに POST を行うシナリオで役立ちます。 アプリに必要なのがアクセス トークンと更新トークンのみである場合は、以下の例から XSRF トークンのコードを削除できます。

通常の Razor Pages または MVC アプリと同様に、アプリを認証します。 トークンをプロビジョニングし、認証 cookie に保存します。

Program ファイルでは:

using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;

...

builder.Services.Configure<OpenIdConnectOptions>(
    OpenIdConnectDefaults.AuthenticationScheme, options =>
{
    options.ResponseType = OpenIdConnectResponseType.Code;
    options.SaveTokens = true;
    options.Scope.Add(OpenIdConnectScope.OfflineAccess);
});

Startup.cs:

using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;

...

services.Configure<OpenIdConnectOptions>(
    OpenIdConnectDefaults.AuthenticationScheme, options =>
{
    options.ResponseType = OpenIdConnectResponseType.Code;
    options.SaveTokens = true;
    options.Scope.Add(OpenIdConnectScope.OfflineAccess);
});

Startup.cs:

using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;

...

services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
    options.ResponseType = OpenIdConnectResponseType.Code;
    options.SaveTokens = true;
    options.Scope.Add(OpenIdConnectScope.OfflineAccess);
});

必要に応じて、options.Scope.Add("{SCOPE}"); を使用して、さらにスコープを追加します。ここで、プレースホルダー {SCOPE} は追加するスコープです。

依存関係の挿入 (DI) からトークンを解決するために Blazor アプリ内で使用できるスコープを持つトークン プロバイダー サービスを定義します。

TokenProvider.cs:

public class TokenProvider
{
    public string? AccessToken { get; set; }
    public string? RefreshToken { get; set; }
    public string? XsrfToken { get; set; }
}

Program ファイルで、次のサービスを追加します。

  • IHttpClientFactory: アクセス トークンを使ってサーバー API から気象データを取得する WeatherForecastService クラスで使われます。
  • TokenProvider: アクセス トークンと更新トークンを保持します。
builder.Services.AddHttpClient();
builder.Services.AddScoped<TokenProvider>();

Startup.ConfigureServicesStartup.cs で、次のサービスを追加します。

  • IHttpClientFactory: アクセス トークンを使ってサーバー API から気象データを取得する WeatherForecastService クラスで使われます。
  • TokenProvider: アクセス トークンと更新トークンを保持します。
services.AddHttpClient();
services.AddScoped<TokenProvider>();

アクセス トークンと更新トークンを使って最初のアプリの状態を渡すクラスを定義します。

InitialApplicationState.cs:

public class InitialApplicationState
{
    public string? AccessToken { get; set; }
    public string? RefreshToken { get; set; }
    public string? XsrfToken { get; set; }
}

Pages/_Host.cshtml ファイルで、InitialApplicationState のインスタンスを作成し、それをパラメーターとしてアプリに渡します。

Pages/_Layout.cshtml ファイルで、InitialApplicationState のインスタンスを作成し、それをパラメーターとしてアプリに渡します。

Pages/_Host.cshtml ファイルで、InitialApplicationState のインスタンスを作成し、それをパラメーターとしてアプリに渡します。

@using Microsoft.AspNetCore.Authentication
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf

...

@{
    var tokens = new InitialApplicationState
    {
        AccessToken = await HttpContext.GetTokenAsync("access_token"),
        RefreshToken = await HttpContext.GetTokenAsync("refresh_token"),
        XsrfToken = Xsrf.GetAndStoreTokens(HttpContext).RequestToken
    };
}

<component ... param-InitialState="tokens" ... />

App コンポーネント (App.razor) で、サービスを解決し、パラメーターからのデータを使用してそれを初期化します。

@inject TokenProvider TokenProvider

...

@code {
    [Parameter]
    public InitialApplicationState? InitialState { get; set; }

    protected override Task OnInitializedAsync()
    {
        TokenProvider.AccessToken = InitialState?.AccessToken;
        TokenProvider.RefreshToken = InitialState?.RefreshToken;
        TokenProvider.XsrfToken = InitialState?.XsrfToken;

        return base.OnInitializedAsync();
    }
}

前の例で TokenProvider に初期状態を割り当てる代わりに、OnInitializedAsync 内でスコープ サービスにデータをコピーしてアプリ全体で使用できます。

Microsoft.AspNet.WebApi.Client NuGet パッケージのパッケージ参照をアプリに追加します。

.NET アプリへのパッケージの追加に関するガイダンスについては、「パッケージ利用のワークフロー」 (NuGet ドキュメント) の "パッケージのインストールと管理" に関する記事を参照してください。 NuGet.org で正しいパッケージ バージョンを確認します。

セキュリティで保護された API 要求を行うサービスで、トークン プロバイダーを挿入し、API 要求のトークンを取得します。

WeatherForecastService.cs:

using System;
using System.Net.Http;
using System.Threading.Tasks;

public class WeatherForecastService
{
    private readonly HttpClient http;
    private readonly TokenProvider tokenProvider;

    public WeatherForecastService(IHttpClientFactory clientFactory, 
        TokenProvider tokenProvider)
    {
        http = clientFactory.CreateClient();
        this.tokenProvider = tokenProvider;
    }

    public async Task<WeatherForecast[]> GetForecastAsync()
    {
        var token = tokenProvider.AccessToken;
        using var request = new HttpRequestMessage(HttpMethod.Get, 
            "https://localhost:5003/WeatherForecast");
        request.Headers.Add("Authorization", $"Bearer {token}");
        using var response = await http.SendAsync(request);
        response.EnsureSuccessStatusCode();

        return await response.Content.ReadFromJsonAsync<WeatherForecast[]>() ?? 
            Array.Empty<WeatherForecast>();
    }
}

コンポーネントに渡される XSRF トークンに対して、TokenProvider を挿入し、POST 要求に XSRF トークンを追加します。 次の例では、ログアウト エンドポイントの POST にトークンを追加します。 次の例のシナリオでは、ログアウト エンドポイント (Areas/Identity/Pages/Account/Logout.cshtmlアプリにスキャフォールディングされている) で IgnoreAntiforgeryTokenAttribute (@attribute [IgnoreAntiforgeryToken]) が指定されていません。保護が必要な通常のログアウト操作に加えて、何らかのアクションが実行されるためです。 エンドポイントでは、要求を正常に処理するために有効な XSRF トークンが必要です。

承認されたユーザーに [Logout] ボタンを表示するコンポーネント:

@inject TokenProvider TokenProvider

...

<AuthorizeView>
    <Authorized>
        <form action="/Identity/Account/Logout?returnUrl=%2F" method="post">
            <button class="nav-link btn btn-link" type="submit">Logout</button>
            <input name="__RequestVerificationToken" type="hidden" 
                value="@TokenProvider.XsrfToken">
        </form>
    </Authorized>
    <NotAuthorized>
        ...
    </NotAuthorized>
</AuthorizeView>

認証スキームを設定する

複数の認証ミドルウェアを使用しているため、複数の認証スキームがあるアプリの場合、Blazor で使用されるスキームを、Program ファイルのエンドポイント構成で明示的に設定できます。 次の例では、OpenID Connect (OIDC) スキームを設定します。

複数の認証ミドルウェアを使用していることにより、複数の認証スキームがあるアプリの場合、Blazor で使用されるスキームを、Startup.cs のエンドポイント構成で明示的に設定できます。 次の例では、OpenID Connect (OIDC) スキームを設定します。

using Microsoft.AspNetCore.Authentication.OpenIdConnect;

...

app.MapRazorComponents<App>().RequireAuthorization(
    new AuthorizeAttribute
    {
        AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme
    })
    .AddInteractiveServerRenderMode();
using Microsoft.AspNetCore.Authentication.OpenIdConnect;

...

app.MapBlazorHub().RequireAuthorization(
    new AuthorizeAttribute 
    {
        AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme
    });

複数の認証ミドルウェアを使用していることにより、複数の認証スキームがあるアプリの場合、Blazor で使用されるスキームを、Startup.Configure のエンドポイント構成で明示的に設定できます。 次の例では、Microsoft Entra ID スキームを設定します。

endpoints.MapBlazorHub().RequireAuthorization(
    new AuthorizeAttribute 
    {
        AuthenticationSchemes = AzureADDefaults.AuthenticationScheme
    });

OpenID Connect (OIDC) v2.0 エンドポイントを使用する

.NET 5 より前のバージョンの ASP.NET Core では、認証ライブラリと Blazor テンプレートで OpenID Connect (OIDC) v1.0 エンドポイントが使用されます。 .NET 5 より前のバージョンの ASP.NET Core で v2.0 エンドポイントを使用するには、OpenIdConnectOptions.AuthorityOpenIdConnectOptions オプションを構成します。

services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, 
    options =>
    {
        options.Authority += "/v2.0";
    }

または、アプリ設定ファイル (appsettings.json) で設定を行うこともできます。

{
  "AzureAd": {
    "Authority": "https://login.microsoftonline.com/common/oauth2/v2.0/",
    ...
  }
}

証明機関へのセグメントを追跡することがアプリの OIDC プロバイダー (ME-ID 以外のプロバイダーなど) にとって適切でない場合は、Authority プロパティを直接設定します。 OpenIdConnectOptions またはアプリ設定ファイルで Authority キーを使用してプロパティを設定します。

コード変更

  • ID トークンの要求のリストは、v2.0 エンドポイントで変更されています。 これらの変更に関する Microsoft ドキュメントは廃止されましたが、ID トークン内の要求に関するガイダンスは、「ID トークンの要求のリファレンス」の中で参照することができます。

  • リソースは v2.0 エンドポイントのスコープ URI で指定されているため、OpenIdConnectOptions.ResourceOpenIdConnectOptions プロパティ設定を削除します。

    services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options => 
        {
            ...
            options.Resource = "...";    // REMOVE THIS LINE
            ...
        }
    

アプリ ID URI

  • v2.0 エンドポイントを使用するとき、API により App ID URI が定義されます。これは API の一意の識別子を表すものです。
  • すべてのスコープにはプレフィックスとしてアプリ ID URI が含まれています。v2.0 エンドポイントからはアプリ ID URI を対象ユーザーとするアクセス トークンが発行されます。
  • v2.0 エンドポイントを使用するとき、Server API で構成されたクライアント ID は API アプリケーション ID (クライアント ID) からアプリ ID URI に変更されます。

appsettings.json:

{
  "AzureAd": {
    ...
    "ClientId": "https://{TENANT}.onmicrosoft.com/{PROJECT NAME}"
    ...
  }
}

使用するアプリ ID URI は、OIDC プロバイダーのアプリ登録の説明で見つけることができます。

カスタム サービスのユーザーをキャプチャするための回線ハンドラー

CircuitHandler からユーザーをキャプチャして、サービスでそのユーザーを設定するには、AuthenticationStateProvider を使います。 ユーザーを更新する場合は、AuthenticationStateChanged にコールバックを登録し、Task をエンキューして新しいユーザーを取得し、サービスを更新します。 このアプローチの例を次に示します。

次に例を示します。

  • 回線が再接続されるたびに OnConnectionUpAsync が呼び出されて、接続の有効期間がユーザーに設定されます。 認証変更用のハンドラーを使って更新を実装しない場合は (次の例では OnConnectionUpAsync)、AuthenticationChanged メソッドのみが必要です。
  • OnCircuitOpenedAsync が呼び出されて、ユーザーを更新するための認証変更ハンドラー AuthenticationChanged がアタッチされます。
  • コード実行のこの時点では例外を報告する方法がないため、catch タスクの UpdateAuthentication ブロックは何も行いません。 タスクから例外がスローされた場合、例外はアプリ内の別の場所で報告されます。

UserService.cs:

using System.Security.Claims;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server.Circuits;

public class UserService
{
    private ClaimsPrincipal currentUser = new(new ClaimsIdentity());

    public ClaimsPrincipal GetUser() => currentUser;

    internal void SetUser(ClaimsPrincipal user)
    {
        if (currentUser != user)
        {
            currentUser = user;
        }
    }
}

internal sealed class UserCircuitHandler(
        AuthenticationStateProvider authenticationStateProvider,
        UserService userService) 
        : CircuitHandler, IDisposable
{
    public override Task OnCircuitOpenedAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        authenticationStateProvider.AuthenticationStateChanged += 
            AuthenticationChanged;

        return base.OnCircuitOpenedAsync(circuit, cancellationToken);
    }

    private void AuthenticationChanged(Task<AuthenticationState> task)
    {
        _ = UpdateAuthentication(task);

        async Task UpdateAuthentication(Task<AuthenticationState> task)
        {
            try
            {
                var state = await task;
                userService.SetUser(state.User);
            }
            catch
            {
            }
        }
    }

    public override async Task OnConnectionUpAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        var state = await authenticationStateProvider.GetAuthenticationStateAsync();
        userService.SetUser(state.User);
    }

    public void Dispose()
    {
        authenticationStateProvider.AuthenticationStateChanged -= 
            AuthenticationChanged;
    }
}
using System.Security.Claims;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server.Circuits;

public class UserService
{
    private ClaimsPrincipal currentUser = new ClaimsPrincipal(new ClaimsIdentity());

    public ClaimsPrincipal GetUser()
    {
        return currentUser;
    }

    internal void SetUser(ClaimsPrincipal user)
    {
        if (currentUser != user)
        {
            currentUser = user;
        }
    }
}

internal sealed class UserCircuitHandler : CircuitHandler, IDisposable
{
    private readonly AuthenticationStateProvider authenticationStateProvider;
    private readonly UserService userService;

    public UserCircuitHandler(
        AuthenticationStateProvider authenticationStateProvider,
        UserService userService)
    {
        this.authenticationStateProvider = authenticationStateProvider;
        this.userService = userService;
    }

    public override Task OnCircuitOpenedAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        authenticationStateProvider.AuthenticationStateChanged += 
            AuthenticationChanged;

        return base.OnCircuitOpenedAsync(circuit, cancellationToken);
    }

    private void AuthenticationChanged(Task<AuthenticationState> task)
    {
        _ = UpdateAuthentication(task);

        async Task UpdateAuthentication(Task<AuthenticationState> task)
        {
            try
            {
                var state = await task;
                userService.SetUser(state.User);
            }
            catch
            {
            }
        }
    }

    public override async Task OnConnectionUpAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        var state = await authenticationStateProvider.GetAuthenticationStateAsync();
        userService.SetUser(state.User);
    }

    public void Dispose()
    {
        authenticationStateProvider.AuthenticationStateChanged -= 
            AuthenticationChanged;
    }
}

Program ファイルでは:

using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.Extensions.DependencyInjection.Extensions;

...

builder.Services.AddScoped<UserService>();
builder.Services.TryAddEnumerable(
    ServiceDescriptor.Scoped<CircuitHandler, UserCircuitHandler>());

Startup.ConfigureServicesStartup.cs で:

using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.Extensions.DependencyInjection.Extensions;

...

services.AddScoped<UserService>();
services.TryAddEnumerable(
    ServiceDescriptor.Scoped<CircuitHandler, UserCircuitHandler>());

コンポーネントでサービスを使って、ユーザーを取得します。

@inject UserService UserService

<h1>Hello, @(UserService.GetUser().Identity?.Name ?? "world")!</h1>

MVC、Razor Pages、その他の ASP.NET Core のシナリオのミドルウェアでユーザーを設定するには、認証ミドルウェアの実行後にカスタム ミドルウェアで SetUser に対して UserService を呼び出すか、IClaimsTransformation の実装でユーザーを設定します。 次の例では、ミドルウェアの方法を使っています。

UserServiceMiddleware.cs:

public class UserServiceMiddleware
{
    private readonly RequestDelegate next;

    public UserServiceMiddleware(RequestDelegate next)
    {
        this.next = next ?? throw new ArgumentNullException(nameof(next));
    }

    public async Task InvokeAsync(HttpContext context, UserService service)
    {
        service.SetUser(context.User);
        await next(context);
    }
}

app.MapRazorComponents<App>() ファイルで Program を呼び出す直前に、ミドルウェアを呼び出します。

app.MapBlazorHub() ファイルで Program を呼び出す直前に、ミドルウェアを呼び出します。

app.MapBlazorHub()Startup.ConfigureStartup.cs を呼び出す直前に、ミドルウェアを呼び出します。

app.UseMiddleware<UserServiceMiddleware>();

送信要求ミドルウェアで AuthenticationStateProvider にアクセスする

AuthenticationStateProvider で作成された DelegatingHandler 用の HttpClient からの IHttpClientFactory には、回線アクティビティ ハンドラーを使用して送信リクエスト ミドルウェアでアクセスできます。

ASP.NET Core アプリで HttpClient を使用して作成された IHttpClientFactory インスタンスごとに HTTP リクエスト用の委任ハンドラーを定義する方法の一般的なガイダンスについては、「ASP.NET Core で IHttpClientFactory を使用して HTTP 要求を行う」における以下のセクションを参照してください。

次の例では、AuthenticationStateProvider を使用して、認証済みユーザーのカスタム ユーザー名ヘッダーを送信要求に添付します。

まず、CircuitServicesAccessor 依存関係の挿入 (DI) に関する記事の以下のセクションで Blazor クラスを実装します。

異なる DI スコープからサーバー側の Blazor サービスにアクセスする

CircuitServicesAccessor を使用して、AuthenticationStateProvider 実装の DelegatingHandler にアクセスします。

AuthenticationStateHandler.cs:

using Microsoft.AspNetCore.Components.Authorization;

public class AuthenticationStateHandler(
    CircuitServicesAccessor circuitServicesAccessor) 
    : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var authStateProvider = circuitServicesAccessor.Services?
            .GetRequiredService<AuthenticationStateProvider>();

        if (authStateProvider is null)
        {
            throw new Exception("AuthenticationStateProvider not available");
        }

        var authState = await authStateProvider.GetAuthenticationStateAsync();

        var user = authState?.User;

        if (user?.Identity is not null && user.Identity.IsAuthenticated)
        {
            request.Headers.Add("X-USER-IDENTITY-NAME", user.Identity.Name);
        }

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

Program ファイルで、AuthenticationStateHandler を登録し、IHttpClientFactory インスタンスを作成する HttpClient にハンドラーを追加します。

builder.Services.AddTransient<AuthenticationStateHandler>();

builder.Services.AddHttpClient("HttpMessageHandler")
    .AddHttpMessageHandler<AuthenticationStateHandler>();