次の方法で共有


Microsoft Entra ID を使用して ASP.NET Core Blazor Web App をセキュリティで保護する

この記事では、サンプル アプリを使用して Microsoft Blazor Web App 用の を使用して、Microsoft Identityでをセキュリティで保護する方法について説明します。

このバージョンの記事では、 フロントエンド用バックエンド (BFF) パターンを採用せずに Entra を実装する方法について説明します。 BFF パターンは、外部サービスに対して認証された要求を行う場合に役立ちます。 アプリの仕様で BFF パターンの採用が必要な場合は、記事のバージョン セレクターを BFF パターン に変更します。

以下の仕様をカバーします。

  • Blazor Web Appでは、グローバル対話機能 (InteractiveAuto) で自動レンダリング モードを使用します。
  • サーバー プロジェクトは AddAuthenticationStateSerialization を呼び出して、 PersistentComponentState を使用して認証状態をクライアントにフローするサーバー側認証状態プロバイダーを追加します。 クライアントは AddAuthenticationStateDeserialization を呼び出して、サーバーによって渡された認証状態を逆シリアル化して使用します。 認証状態は、WebAssembly アプリケーションの有効期間中は変わりません。
  • アプリは、Microsoft パッケージに基づいて Microsoft Identity使用します。
  • 非対話型トークンの自動更新は、フレームワークによって管理されます。
  • このアプリでは、サーバー側とクライアント側のサービスの抽象化を使用して、生成された気象データを表示します。
    • Weather コンポーネントをサーバーにレンダリングして気象データを表示する場合、コンポーネントはServerWeatherForecasterを使用します。 Microsoft Identity Web パッケージには、Web API 呼び出しを行う名前付きダウンストリーム Web サービスを作成するための API が用意されています。 IDownstreamApiは、外部 Web API (ServerWeatherForecaster プロジェクト) から気象データを取得するためにCallApiForUserAsyncを呼び出すために使用されるMinimalApiJwtに挿入されます。
    • Weather コンポーネントがクライアントにレンダリングされると、コンポーネントは ClientWeatherForecaster サービス実装を使用します。この実装では、事前構成済みのHttpClient (クライアント プロジェクトのProgram ファイル内) を使用して、気象データ用にサーバー プロジェクトの Minimal API (/weather-forecast) への Web API 呼び出しが行われます。 Minimal API エンドポイントは、 ServerWeatherForecaster クラスから気象データを取得し、コンポーネントによってレンダリングするためにクライアントに返します。

サンプル ソリューション

サンプル ソリューションは、次のプロジェクトで構成されています。

  • BlazorWebAppEntra: 気象データのBlazor Web App エンドポイントの例を含む、のサーバー側プロジェクト。
  • BlazorWebAppEntra.Client: Blazor Web App のクライアント側プロジェクト。
  • MinimalApiJwt: 気象データの 最小 API エンドポイントの例を含むバックエンド Web API。

次のリンクを使用して、Blazor サンプル リポジトリの最新バージョン フォルダーからサンプルにアクセスします。 サンプルは、.NET 9 以降の BlazorWebAppEntra フォルダーにあります。

Aspire/Aspire.AppHost プロジェクトからソリューションを開始します。

サンプル コードを表示またはダウンロードする (ダウンロード方法)

Microsoft Entra ID アプリの登録

アプリと Web API が同じソリューション内にある場合でも、アプリと Web API には個別の登録を使用することをお勧めします。 次のガイダンスは、サンプル ソリューションの BlazorWebAppEntra アプリと MinimalApiJwt Web API を対象としていますが、一般に、アプリと Web API の Entra ベースの登録にも同じガイダンスが適用されます。

最初に Web API (MinimalApiJwt) を登録して、アプリの登録時に Web API へのアクセスを許可できるようにします。 Web API のテナント ID とクライアント ID は、 Program ファイルで Web API を構成するために使用されます。 Web API を登録した後、アプリの登録で Web API を公開します>スコープ名がWeather.Getします。 アプリの構成で使用するアプリ ID URI を記録します。

次に、BlazorWebAppEntra プラットフォーム構成とリダイレクト URI (ポートは必要ありません) にアプリ (https://localhost/signin-oidc) を登録します。 アプリのテナント ID、テナント ドメイン、クライアント ID、および Web API のベース アドレス、アプリ ID URI、天気スコープ名は、 appsettings.json ファイルでアプリを構成するために使用されます。 アプリ登録で>API 許可を与えて、Web API にアクセスします。 アプリのセキュリティ仕様で呼び出される場合は、組織が Web API にアクセスするための管理者の同意を付与できます。 承認されたユーザーとグループは、アプリ登録>およびエンタープライズアプリケーションにおけるアプリの登録に割り当てられます。

Entra または Azure portal の 暗黙的な許可とハイブリッド フロー のアプリ登録構成で、 アクセス トークン または ID トークンを返す承認エンドポイントのチェック ボックスをオンにしないでください。 OpenID Connect ハンドラーは、承認エンドポイントから返されたコードを使用して、適切なトークンを自動的に要求します。

Entra または Azure のポータル (管理>証明書とシークレット>新しいクライアント シークレット) でアプリの登録にクライアント シークレットを作成します。 次のセクションで使用するには、クライアント シークレット の値 を保持します。

特定の設定に関する追加の Entra 構成ガイダンスについては、この記事の後半で説明します。

サーバー側の Blazor Web App プロジェクト (BlazorWebAppEntra)

BlazorWebAppEntra プロジェクトは、Blazor Web App のサーバー側プロジェクトです。

クライアント側 Blazor Web App プロジェクト (BlazorWebAppEntra.Client)

BlazorWebAppEntra.Client プロジェクトは、Blazor Web App のクライアント側プロジェクトです。

ユーザーがクライアント側のレンダリング中にログインまたはログアウトする必要がある場合は、ページ全体の再読み込みが開始されます。

バックエンド Web API プロジェクト (MinimalApiJwt)

MinimalApiJwt プロジェクトは、複数のフロントエンド プロジェクト用のバックエンド Web API です。 このプロジェクトでは、気象データ用の 最小限の API エンドポイントを構成します。

MinimalApiJwt.http ファイルは、気象データの要求のテストに使用できます。 エンドポイントをテストするには MinimalApiJwt プロジェクトが実行されている必要があり、エンドポイントはファイルにハードコーディングされていることに注意してください。 詳細については、「 Visual Studio 2022 での .http ファイルの使用」を参照してください。

プロジェクトには、 開発環境で OpenAPI ドキュメントSwagger UI を生成するためのパッケージと構成が含まれています。 詳細については、「 生成された OpenAPI ドキュメントを使用する」を参照してください。

安全な天気予報データ エンドポイントは、プロジェクトの Program ファイルにあります。

app.MapGet("/weather-forecast", () =>
{
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
    return forecast;
}).RequireAuthorization();

RequireAuthorization 拡張メソッドでは、ルート定義の認可が必要です。 プロジェクトに追加するすべてのコントローラーに対して、 [Authorize] 属性 をコントローラーまたはアクションに追加します。

バックエンド Web API プロジェクトを構成する (MinimalApiJwt)

JwtBearerOptions プロジェクトのAddJwtBearer ファイルのMinimalApiJwt呼び出しのProgramでプロジェクトを構成します。

Web API アプリの登録の場合、 Weather.Get スコープは Entra または Azure portal の [API の公開] で構成されます。

Authority OIDC 呼び出しを行う権限を設定します。

jwtOptions.Authority = "{AUTHORITY}";

次の例では、aaaabbbb-0000-cccc-1111-dddd2222eeeeのテナント ID を使用します。

アプリが ME-ID テナントに登録されている場合、機関はアイデンティティプロバイダーによって返されるJWTの発行者(iss)と一致しなければなりません。

jwtOptions.Authority = "https://sts.windows.net/aaaabbbb-0000-cccc-1111-dddd2222eeee/";

アプリが AAD B2C テナントに登録されている場合:

jwtOptions.Authority = "https://login.microsoftonline.com/aaaabbbb-0000-cccc-1111-dddd2222eeee/v2.0/";

Audience は、受信した JWT アクセス トークンの対象ユーザーを設定します。

jwtOptions.Audience = "{AUDIENCE}";

Entra または Azure portal の [API の公開] でWeather.Get スコープを追加するときに構成されたアプリケーション ID URI のパスのみに値を一致させます。 値にスコープ名 "Weather.Get" を含めないでください。

次の例では、 11112222-bbbb-3333-cccc-4444dddd5555のアプリケーション (クライアント) ID を使用します。 2 番目の例では、 contoso.onmicrosoft.comのテナント ドメインを使用します。

ME-ID テナントの例:

jwtOptions.Audience = "api://11112222-bbbb-3333-cccc-4444dddd5555";

AAD B2C テナントの例:

jwtOptions.Audience = "https://contoso.onmicrosoft.com/11112222-bbbb-3333-cccc-4444dddd5555";

サーバー プロジェクトを構成する (BlazorWebAppEntra)

AddMicrosoftIdentityWebApp Microsoft Identity Web (Microsoft.Identity.Web NuGet パッケージAPI ドキュメント) は、BlazorWebAppEntraプロジェクトのProgramファイルで設定されています。

Entra または Azure portal でアプリの登録から、アプリケーション (クライアント) ID、テナント (発行元) ドメイン、およびディレクトリ (テナント) ID を取得します。 アプリ ID URI は、Web API の登録から Weather.Get スコープに対して取得されます。 ポータルからアプリ ID URI を取得するときは、スコープ名を含めないでください。

BlazorWebAppEntra プロジェクトのProgram ファイルで、Microsoft Identity Web 構成の次のプレースホルダーの値を指定します。

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(msIdentityOptions =>
    {
        msIdentityOptions.CallbackPath = "/signin-oidc";
        msIdentityOptions.ClientId = "{CLIENT ID (BLAZOR APP)}";
        msIdentityOptions.Domain = "{DIRECTORY NAME}.onmicrosoft.com";
        msIdentityOptions.Instance = "https://login.microsoftonline.com/";
        msIdentityOptions.ResponseType = "code";
        msIdentityOptions.TenantId = "{TENANT ID}";
    })
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDownstreamApi("DownstreamApi", configOptions =>
    {
        configOptions.BaseUrl = "{BASE ADDRESS}";
        configOptions.Scopes = [ "{APP ID URI}/Weather.Get" ];
    })
    .AddDistributedTokenCaches();

前の構成のプレースホルダー:

  • {CLIENT ID (BLAZOR APP)}: アプリケーション (クライアント) ID。
  • {DIRECTORY NAME}: テナント (パブリッシャー) ドメインのディレクトリ名。
  • {TENANT ID}: ディレクトリ (テナント) ID。
  • {BASE ADDRESS}: Web API のベース アドレス。
  • {APP ID URI}: Web API スコープのアプリ ID URI。 次のいずれかの形式が使用されます。ここで、 {CLIENT ID (WEB API)} プレースホルダーは Web API の Entra 登録のクライアント ID、 {DIRECTORY NAME} プレースホルダーはテナント (パブリッシャー) ドメインのディレクトリ名です (例: contoso)。
    • ME-ID テナントの形式: api://{CLIENT ID (WEB API)}
    • B2C テナント形式: https://{DIRECTORY NAME}.onmicrosoft.com/{CLIENT ID (WEB API)}

例:

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(msIdentityOptions =>
    {
        msIdentityOptions.CallbackPath = "/signin-oidc";
        msIdentityOptions.ClientId = "00001111-aaaa-2222-bbbb-3333cccc4444";
        msIdentityOptions.Domain = "contoso.onmicrosoft.com";
        msIdentityOptions.Instance = "https://login.microsoftonline.com/";
        msIdentityOptions.ResponseType = "code";
        msIdentityOptions.TenantId = "aaaabbbb-0000-cccc-1111-dddd2222eeee";
    })
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDownstreamApi("DownstreamApi", configOptions =>
    {
        configOptions.BaseUrl = "https://localhost:7277";
        configOptions.Scopes = [ "api://11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get" ];
    })
    .AddDistributedTokenCaches();

このバージョンの記事では、 フロントエンド用バックエンド (BFF) パターンを使用した Entra の実装について説明します。 アプリの仕様で BFF パターンの採用が必要ない場合は、記事のバージョン セレクターを BFF 以外のパターン に変更します。

以下の仕様をカバーします。

  • Blazor Web Appでは、グローバル対話機能 (InteractiveAuto) で自動レンダリング モードを使用します。
  • サーバー プロジェクトは AddAuthenticationStateSerialization を呼び出して、 PersistentComponentState を使用して認証状態をクライアントにフローするサーバー側認証状態プロバイダーを追加します。 クライアントは AddAuthenticationStateDeserialization を呼び出して、サーバーによって渡された認証状態を逆シリアル化して使用します。 認証状態は、WebAssembly アプリケーションの有効期間中は変わりません。
  • アプリは、Microsoft パッケージに基づいて Microsoft Identity使用します。
  • 非対話型トークンの自動更新は、フレームワークによって管理されます。
  • バックエンド のフロントエンド (BFF) パターンは、サービス検出に.NET Aspireを使用し、バックエンド アプリの天気予報エンドポイントに要求をプロキシする場合は YARP を使用して採用されます。
    • バックエンド Web API は、JWT ベアラー認証を使用して、サインイン Blazor Web App の cookie によって保存された JWT トークンを検証します。
    • Aspire は、.NET クラウドネイティブ アプリを構築するエクスペリエンスを強化します。 これは、分散型アプリを構築して実行するための、一貫性のある、厳格なツールとパターンのセットを提供します。
    • YARP (Yet Another Reverse Proxy) は、リバース プロキシ サーバーの作成に使用されるライブラリです。
  • このアプリでは、サーバー側とクライアント側のサービスの抽象化を使用して、生成された気象データを表示します。
    • Weather コンポーネントをサーバーにレンダリングして気象データを表示する場合、コンポーネントはServerWeatherForecasterを使用します。 Microsoft Identity Web パッケージには、Web API 呼び出しを行う名前付きダウンストリーム Web サービスを作成するための API が用意されています。 IDownstreamApiは、外部 Web API (ServerWeatherForecaster プロジェクト) から気象データを取得するためにCallApiForUserAsyncを呼び出すために使用されるMinimalApiJwtに挿入されます。
    • Weather コンポーネントがクライアントにレンダリングされると、コンポーネントは ClientWeatherForecaster サービス実装を使用します。この実装では、事前構成済みのHttpClient (クライアント プロジェクトのProgram ファイル内) を使用して、気象データ用にサーバー プロジェクトの Minimal API (/weather-forecast) への Web API 呼び出しが行われます。 最小 API エンドポイントは、 GetAccessTokenForUserAsyncを呼び出してユーザーのアクセス トークンを取得します。 正しいスコープと共に、外部 Web API (MinimalApiJwt プロジェクト) に対してリバース プロキシ呼び出しが行われ、コンポーネントによってレンダリングするために気象データが取得され、クライアントに返されます。

.NET Aspireの詳細については、「.NET Aspireの一般提供: .NET Cloud-Native 開発の簡略化 (2024 年 5 月)」を参照してください。

前提条件

.NET Aspire には Visual Studio バージョン 17.10 以降が必要です。

また、「をビルドする」の「.NET Aspire」セクションを参照してください。

サンプル ソリューション

サンプル ソリューションは、次のプロジェクトで構成されています。

  • .NET Aspire:
    • Aspire.AppHost: アプリの高度なオーケストレーションの問題を管理するために使用されます。
    • Aspire.ServiceDefaults: 必要に応じて拡張してカスタマイズできる ..NET Aspire の既定のアプリ構成が含まれます。
  • MinimalApiJwt: 気象データの 最小 API エンドポイントの例を含むバックエンド Web API。
  • BlazorWebAppEntra: Blazor Web App のサーバー側プロジェクト。
  • BlazorWebAppEntra.Client: Blazor Web App のクライアント側プロジェクト。

次のリンクを使用して、Blazor サンプル リポジトリの最新バージョン フォルダーからサンプルにアクセスします。 サンプルは、.NET 9 以降の BlazorWebAppEntraBff フォルダーにあります。

サンプル コードを表示またはダウンロードする (ダウンロード方法)

Microsoft Entra ID アプリの登録

アプリと Web API が同じソリューション内にある場合でも、アプリと Web API には個別の登録を使用することをお勧めします。 次のガイダンスは、サンプル ソリューションの BlazorWebAppEntra アプリと MinimalApiJwt Web API を対象としていますが、一般に、アプリと Web API の Entra ベースの登録にも同じガイダンスが適用されます。

最初に Web API (MinimalApiJwt) を登録して、アプリの登録時に Web API へのアクセスを許可できるようにします。 Web API のテナント ID とクライアント ID は、 Program ファイルで Web API を構成するために使用されます。 Web API を登録した後、アプリの登録で Web API を公開します>スコープ名がWeather.Getします。 アプリの構成で使用するアプリ ID URI を記録します。

次に、BlazorWebAppEntra プラットフォーム構成とリダイレクト URI (ポートは必要ありません) にアプリ (https://localhost/signin-oidc) を登録します。 アプリのテナント ID、テナント ドメイン、クライアント ID、および Web API のベース アドレス、アプリ ID URI、天気スコープ名は、 appsettings.json ファイルでアプリを構成するために使用されます。 アプリ登録で>API 許可を与えて、Web API にアクセスします。 アプリのセキュリティ仕様で呼び出される場合は、組織が Web API にアクセスするための管理者の同意を付与できます。 承認されたユーザーとグループは、アプリ登録>およびエンタープライズアプリケーションにおけるアプリの登録に割り当てられます。

Entra または Azure portal の 暗黙的な許可とハイブリッド フロー のアプリ登録構成で、 アクセス トークン または ID トークンを返す承認エンドポイントのチェック ボックスをオンにしないでください。 OpenID Connect ハンドラーは、承認エンドポイントから返されたコードを使用して、適切なトークンを自動的に要求します。

Entra または Azure のポータル (管理>証明書とシークレット>新しいクライアント シークレット) でアプリの登録にクライアント シークレットを作成します。 次のセクションで使用するには、クライアント シークレット の値 を保持します。

特定の設定に関する追加の Entra 構成ガイダンスについては、この記事の後半で説明します。

.NET Aspire プロジェクト

.NET Aspireの使用の詳細と、サンプル アプリの.AppHostおよび.ServiceDefaults プロジェクトの詳細については、.NET Aspireドキュメントを参照してください

.NET Aspire の前提条件を満たしていることを確認します。 詳細については、「」の.NET Aspireセクションをご覧ください。

サンプル アプリでは、開発テスト中に使用する、安全でない HTTP 起動プロファイル (http) のみが構成されます。 安全でない安全な起動設定プロファイルの例を含む詳細については、「.NET Aspireで安全でないトランスポートを許可する (.NET Aspire ドキュメント)」を参照してください。

サーバー側の Blazor Web App プロジェクト (BlazorWebAppEntra)

BlazorWebAppEntra プロジェクトは、Blazor Web App のサーバー側プロジェクトです。

クライアント側 Blazor Web App プロジェクト (BlazorWebAppEntra.Client)

BlazorWebAppEntra.Client プロジェクトは、Blazor Web App のクライアント側プロジェクトです。

ユーザーがクライアント側のレンダリング中にログインまたはログアウトする必要がある場合は、ページ全体の再読み込みが開始されます。

バックエンド Web API プロジェクト (MinimalApiJwt)

MinimalApiJwt プロジェクトは、複数のフロントエンド プロジェクト用のバックエンド Web API です。 このプロジェクトでは、気象データ用の 最小限の API エンドポイントを構成します。 Blazor Web App サーバー側プロジェクト (BlazorWebAppEntra) からの要求は、MinimalApiJwt プロジェクトにプロキシされます。

MinimalApiJwt.http ファイルは、気象データの要求のテストに使用できます。 エンドポイントをテストするには MinimalApiJwt プロジェクトが実行されている必要があり、エンドポイントはファイルにハードコーディングされていることに注意してください。 詳細については、「 Visual Studio 2022 での .http ファイルの使用」を参照してください。

プロジェクトには、 開発環境で OpenAPI ドキュメントSwagger UI を生成するためのパッケージと構成が含まれています。 詳細については、「 生成された OpenAPI ドキュメントを使用する」を参照してください。

安全な天気予報データ エンドポイントは、プロジェクトの Program ファイルにあります。

app.MapGet("/weather-forecast", () =>
{
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
    return forecast;
}).RequireAuthorization();

RequireAuthorization 拡張メソッドでは、ルート定義の認可が必要です。 プロジェクトに追加するすべてのコントローラーに対して、 [Authorize] 属性 をコントローラーまたはアクションに追加します。

バックエンド Web API プロジェクトを構成する (MinimalApiJwt)

プロジェクトのMinimalApiJwt ファイル内のJwtBearerOptions呼び出しのAddJwtBearerで、Program プロジェクトを構成します。

Web API アプリの登録の場合、 Weather.Get スコープは Entra または Azure portal の [API の公開] で構成されます。

Authority OIDC 呼び出しを行う権限を設定します。

jwtOptions.Authority = "{AUTHORITY}";

次の例では、aaaabbbb-0000-cccc-1111-dddd2222eeeeのテナント ID を使用します。

アプリが ME-ID テナントに登録されている場合、機関はアイデンティティプロバイダーによって返されるJWTの発行者(iss)と一致しなければなりません。

jwtOptions.Authority = "https://sts.windows.net/aaaabbbb-0000-cccc-1111-dddd2222eeee/";

アプリが AAD B2C テナントに登録されている場合:

jwtOptions.Authority = "https://login.microsoftonline.com/aaaabbbb-0000-cccc-1111-dddd2222eeee/v2.0/";

Audience は、受信した JWT アクセス トークンの対象ユーザーを設定します。

jwtOptions.Audience = "{AUDIENCE}";

Entra または Azure portal の [API の公開] でWeather.Get スコープを追加するときに構成されたアプリケーション ID URI のパスのみに値を一致させます。 値にスコープ名 "Weather.Get" を含めないでください。

次の例では、 11112222-bbbb-3333-cccc-4444dddd5555のアプリケーション (クライアント) ID を使用します。 2 番目の例では、 contoso.onmicrosoft.comのテナント ドメインを使用します。

ME-ID テナントの例:

jwtOptions.Audience = "api://11112222-bbbb-3333-cccc-4444dddd5555";

AAD B2C テナントの例:

jwtOptions.Audience = "https://contoso.onmicrosoft.com/11112222-bbbb-3333-cccc-4444dddd5555";

サーバー プロジェクトを構成する (BlazorWebAppEntra)

AddMicrosoftIdentityWebApp Microsoft Identity Web (Microsoft.Identity.Web NuGet パッケージAPI ドキュメント) は、BlazorWebAppEntraプロジェクトのProgramファイルで設定されています。

Entra または Azure portal でアプリの登録から、アプリケーション (クライアント) ID、テナント (発行元) ドメイン、およびディレクトリ (テナント) ID を取得します。 アプリ ID URI は、Web API の登録から Weather.Get スコープに対して取得されます。 ポータルからアプリ ID URI を取得するときは、スコープ名を含めないでください。

BlazorWebAppEntra プロジェクトのProgram ファイルで、Microsoft Identity Web 構成の次のプレースホルダーの値を指定します。

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(msIdentityOptions =>
    {
        msIdentityOptions.CallbackPath = "/signin-oidc";
        msIdentityOptions.ClientId = "{CLIENT ID (BLAZOR APP)}";
        msIdentityOptions.Domain = "{DIRECTORY NAME}.onmicrosoft.com";
        msIdentityOptions.Instance = "https://login.microsoftonline.com/";
        msIdentityOptions.ResponseType = "code";
        msIdentityOptions.TenantId = "{TENANT ID}";
    })
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDownstreamApi("DownstreamApi", configOptions =>
    {
        configOptions.BaseUrl = "{BASE ADDRESS}";
        configOptions.Scopes = [ "{APP ID URI}/Weather.Get" ];
    })
    .AddDistributedTokenCaches();

前の構成のプレースホルダー:

  • {CLIENT ID (BLAZOR APP)}: アプリケーション (クライアント) ID。
  • {DIRECTORY NAME}: テナント (パブリッシャー) ドメインのディレクトリ名。
  • {TENANT ID}: ディレクトリ (テナント) ID。
  • {BASE ADDRESS}: Web API のベース アドレス。
  • {APP ID URI}: Web API スコープのアプリ ID URI。 次のいずれかの形式が使用されます。ここで、 {CLIENT ID (WEB API)} プレースホルダーは Web API の Entra 登録のクライアント ID、 {DIRECTORY NAME} プレースホルダーはテナント (パブリッシャー) ドメインのディレクトリ名です (例: contoso)。
    • ME-ID テナントの形式: api://{CLIENT ID (WEB API)}
    • B2C テナント形式: https://{DIRECTORY NAME}.onmicrosoft.com/{CLIENT ID (WEB API)}

例:

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(msIdentityOptions =>
    {
        msIdentityOptions.CallbackPath = "/signin-oidc";
        msIdentityOptions.ClientId = "00001111-aaaa-2222-bbbb-3333cccc4444";
        msIdentityOptions.Domain = "contoso.onmicrosoft.com";
        msIdentityOptions.Instance = "https://login.microsoftonline.com/";
        msIdentityOptions.ResponseType = "code";
        msIdentityOptions.TenantId = "aaaabbbb-0000-cccc-1111-dddd2222eeee";
    })
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDownstreamApi("DownstreamApi", configOptions =>
    {
        configOptions.BaseUrl = "https://localhost:7277";
        configOptions.Scopes = [ "api://11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get" ];
    })
    .AddDistributedTokenCaches();

警告

運用アプリでは、運用分散トークン キャッシュ プロバイダーを使用する必要があります。 そうしないと、一部のシナリオでアプリのパフォーマンスが低下する可能性があります。 詳細については、「 運用分散トークン キャッシュ プロバイダーの使用 」セクションを参照してください。

コールバック パス (CallbackPath) は、Entra または Azure portal でアプリケーションを登録するときに構成されたリダイレクト URI (ログイン コールバック パス) と一致する必要があります。 パスは、アプリの登録の [認証 ] ブレードで構成されます。 CallbackPathの既定値は、登録済みリダイレクト URI が/signin-oidcの場合、https://localhost/signin-oidcです (ポートは必要ありません)。

SignedOutCallbackPathは、Entra からサインアウトした後、ユーザーエージェントが最初に戻る際に、OpenID Connect ハンドラーによってアプリのベースパス内で処理されるリクエストパスです。 既定値の "/signout-callback-oidc" が使用されるため、サンプル アプリではパスの値が設定されません。 要求をインターセプトした後、OpenID Connect ハンドラーは、指定されている場合は、SignedOutRedirectUri または RedirectUriにリダイレクトします。

アプリの Entra 登録でサインアウト用のコールバックパスを構成します。 Entra または Azure portal で、 Web プラットフォーム構成の リダイレクト URI エントリのパスを設定します。

https://localhost/signout-callback-oidc

Entra を使用する場合、localhost アドレスにはポートは必要ありません。

Entra でアプリの登録にサインアウトされたコールバック パス URI を追加しない場合、Entra はユーザーをアプリにリダイレクトすることを拒否し、単にブラウザー ウィンドウを閉じるよう要求します。

Entra は、プライマリ管理者ユーザー (ルート アカウント) または外部ユーザーを Blazor アプリケーションにリダイレクトしません。 代わりに、Entra はユーザーをアプリからログアウトさせ、すべてのブラウザー ウィンドウを閉じるように勧めます。 詳細については、「 authority url にテナント ID が含まれる場合に postLogoutRedirectUri が機能しない問題 ( #5783)」を参照してください。

警告

アプリ シークレット、接続文字列、資格情報、パスワード、個人識別番号 (PIN)、プライベート C#/.NET コード、秘密キー/トークンをクライアント側コードに格納しないでください。これは 常に安全ではありません。 テスト/ステージング環境と運用環境では、サーバー側の Blazor コードと Web API は、プロジェクト コードまたは構成ファイル内で資格情報を維持しないように、セキュリティで保護された認証フローを使用する必要があります。 ローカル開発テスト以外では、環境変数が最も安全なアプローチではないため、環境変数を使用して機密データを格納しないようにすることをお勧めします。 ローカル開発テストでは、機密データをセキュリティで保護するために Secret Manager ツール を使用することをお勧めします。 詳細については、「 機密データと資格情報を安全に管理する」を参照してください。

クライアント シークレットを確立する

このセクションは、 Blazor Web Appのサーバー プロジェクトにのみ適用されます。

次のいずれかの方法または両方の方法を使用して、クライアント シークレットをアプリに提供します。

  • Secret Manager ツール: Secret Manager ツールは、ローカル コンピューターにプライベート データを格納し、ローカル開発時にのみ使用します。
  • Azure Key Vault: ローカルで作業する場合の開発環境を含め、任意の環境で使用するために、クライアント シークレットをキー コンテナーに格納できます。 一部の開発者は、ステージングおよび運用環境のデプロイにキー コンテナーを使用し、ローカル開発に Secret Manager ツールを使用することを好みます。

クライアント シークレットをプロジェクト コードまたは構成ファイルに格納しないことを強くお勧めします。 このセクションの方法のいずれかまたは両方など、セキュリティで保護された認証フローを使用します。

シークレット マネージャー ツール

Secret Manager ツールは、サーバー アプリのクライアント シークレットを構成キー AzureAd:ClientSecretの下に格納できます。

Blazor サーバー アプリがシークレット マネージャー ツール用に初期化されていません。 Visual Studio の Developer PowerShell コマンド シェルなどのコマンド シェルを使用して、次のコマンドを実行します。 コマンドを実行する前に、 cd コマンドを使用してサーバー プロジェクトのディレクトリにディレクトリを変更します。 このコマンドは、サーバー アプリのプロジェクト ファイルにユーザー シークレット識別子 (<UserSecretsId>) を確立します。これは、アプリのシークレットを追跡するためにツールによって内部的に使用されます。

dotnet user-secrets init

次のコマンドを実行して、クライアント シークレットを設定します。 {SECRET} プレースホルダーは、アプリの Entra 登録から取得したクライアント シークレットです。

dotnet user-secrets set "AzureAd:ClientSecret" "{SECRET}"

Visual Studio を使用している場合は、 ソリューション エクスプローラー でサーバー プロジェクトを右クリックし、[ ユーザー シークレットの管理] を選択することで、シークレットが設定されていることを確認できます。

Azure Key Vault

Azure Key Vault は、アプリのクライアント シークレットをアプリに提供するための安全なアプローチを提供します。

キー コンテナーを作成してクライアント シークレットを設定するには、 Azure Key Vault シークレットについて (Azure ドキュメント) を参照してください。Azure Key Vault の使用を開始するためにリソースをクロスリンクします。 このセクションのコードを実装するには、キー コンテナーとシークレットを作成するときに、Azure のキー コンテナー URI とシークレット名を記録します。 アクセス ポリシー パネルでシークレットのアクセス ポリシー を設定する場合:

  • シークレット権限の取得のみが必要です。
  • シークレットの プリンシパル としてアプリケーションを選択します。

重要

キー ボールト シークレットは、有効期限を設定して作成されます。 キー コンテナー シークレットの有効期限が切れるタイミングを追跡し、その日付が経過する前にアプリ用に新しいシークレットを作成します。

次の AzureHelper クラスをサーバー プロジェクトに追加します。 GetKeyVaultSecret メソッドは、キー ボールトからシークレットを取得します。 プロジェクトの名前空間スキームに合わせて名前空間 (BlazorSample.Helpers) を調整します。

Helpers/AzureHelper.cs:

using Azure;
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;

namespace BlazorWebAppEntra.Helpers;

public static class AzureHelper
{
    public static string GetKeyVaultSecret(string tenantId, string vaultUri, string secretName)
    {
        DefaultAzureCredentialOptions options = new()
        {
            // Specify the tenant ID to use the dev credentials when running the app locally
            // in Visual Studio.
            VisualStudioTenantId = tenantId,
            SharedTokenCacheTenantId = tenantId
        };

        var client = new SecretClient(new Uri(vaultUri), new DefaultAzureCredential(options));
        var secret = client.GetSecretAsync(secretName).Result;

        return secret.Value.Value;
    }
}

サービスがサーバー プロジェクトの Program ファイルに登録されている場合は、次のコードを使用してクライアント シークレットを取得して適用します。

var tenantId = builder.Configuration.GetValue<string>("AzureAd:TenantId")!;
var vaultUri = builder.Configuration.GetValue<string>("AzureAd:VaultUri")!;
var secretName = builder.Configuration.GetValue<string>("AzureAd:SecretName")!;

builder.Services.Configure<MicrosoftIdentityOptions>(
    OpenIdConnectDefaults.AuthenticationScheme,
    options =>
    {
        options.ClientSecret = 
            AzureHelper.GetKeyVaultSecret(tenantId, vaultUri, secretName);
    });

ローカル開発に Secret Manager ツールを使用することを選択したためにコードをローカルで実行しないようにするなど、上記のコードが動作する環境を制御する場合は、環境をチェックする条件付きステートメントで上記のコードをラップできます。

if (!context.HostingEnvironment.IsDevelopment())
{
    ...
}

AzureAdappsettings.json セクションで、次のVaultUriSecretName構成キーと値を追加します。

"VaultUri": "{VAULT URI}",
"SecretName": "{SECRET NAME}"

前の例の場合:

  • {VAULT URI} プレースホルダーは、キー コンテナーの URI です。 URI に末尾のスラッシュを含めます。
  • {SECRET NAME} プレースホルダーはシークレット名です。

例:

"VaultUri": "https://contoso.vault.azure.net/",
"SecretName": "BlazorWebAppEntra"

構成は、アプリの環境構成ファイルに基づいて専用のキー コンテナーとシークレット名を簡単に指定するために使用されます。 たとえば、開発中の appsettings.Development.json 、ステージング時の appsettings.Staging.json 、運用デプロイの appsettings.Production.json に対して、さまざまな構成値を指定できます。 詳細については、「 ASP.NET Core Blazor 構成」を参照してください。

名前とロール要求のみをシリアル化する

Program ファイルでは、SerializeAllClaimstrue に設定することで、すべての要求がシリアル化されます。 CSR の名前とロール要求のみをシリアル化する場合は、オプションを削除するか、 falseに設定します。

JSON 構成プロバイダーを使用して構成を指定する (アプリ設定)

サンプル ソリューション プロジェクトでは、C# オートコンプリートを使用して構成設定を検出できるように、Identity ファイルで Microsoft Program Web と JWT ベアラー認証を構成します。 通常、プロフェッショナル アプリは 、既定の JSON 構成プロバイダー などの OIDC オプションを 構成するために構成プロバイダーを使用します。 JSON 構成プロバイダーは、アプリ設定ファイル appsettings.json/appsettings.{ENVIRONMENT}.jsonから構成を読み込みます。ここで、 {ENVIRONMENT} プレースホルダーはアプリの ランタイム環境です。 このセクションのガイダンスに従って、構成にアプリ設定ファイルを使用します。

appsettings.json プロジェクトのアプリ設定ファイル (BlazorWebAppEntra) に、次の JSON 構成を追加します。

{
  "AzureAd": {
    "CallbackPath": "/signin-oidc",
    "ClientId": "{CLIENT ID (BLAZOR APP)}",
    "Domain": "{DIRECTORY NAME}.onmicrosoft.com",
    "Instance": "https://login.microsoftonline.com/",
    "ResponseType": "code",
    "TenantId": "{TENANT ID}"
  },
  "DownstreamApi": {
    "BaseUrl": "{BASE ADDRESS}",
    "Scopes": [ "{APP ID URI}/Weather.Get" ]
  }
}

上記の構成のプレースホルダーを、 Program ファイルでアプリが使用する値と一致するように更新します。

  • {CLIENT ID (BLAZOR APP)}: アプリケーション (クライアント) ID。
  • {DIRECTORY NAME}: テナント (パブリッシャー) ドメインのディレクトリ名。
  • {TENANT ID}: ディレクトリ (テナント) ID。
  • {BASE ADDRESS}: Web API のベース アドレス。
  • {APP ID URI}: Web API スコープのアプリ ID URI。 次のいずれかの形式が使用されます。ここで、 {CLIENT ID (WEB API)} プレースホルダーは Web API の Entra 登録のクライアント ID、 {DIRECTORY NAME} プレースホルダーはテナント (パブリッシャー) ドメインのディレクトリ名です (例: contoso)。
    • ME-ID テナントの形式: api://{CLIENT ID (WEB API)}
    • B2C テナント形式: https://{DIRECTORY NAME}.onmicrosoft.com/{CLIENT ID (WEB API)}

例:

"AzureAd": {
  "CallbackPath": "/signin-oidc",
  "ClientId": "00001111-aaaa-2222-bbbb-3333cccc4444",
  "Domain": "contoso.onmicrosoft.com",
  "Instance": "https://login.microsoftonline.com/",
  "ResponseType": "code",
  "TenantId": "aaaabbbb-0000-cccc-1111-dddd2222eeee"
},
"DownstreamApi": {
  "BaseUrl": "https://localhost:7277",
  "Scopes": [ "api://11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get" ]
}

上記の構成内の他の値を、 Program ファイルで使用されているカスタム値または既定値以外の値と一致するように更新します。

構成は、認証ビルダーによって自動的に取得されます。

Program ファイルを次のように変更します。

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
-   .AddMicrosoftIdentityWebApp(msIdentityOptions =>
-   {
-       msIdentityOptions.CallbackPath = "...";
-       msIdentityOptions.ClientId = "...";
-       msIdentityOptions.Domain = "...";
-       msIdentityOptions.Instance = "...";
-       msIdentityOptions.ResponseType = "...";
-       msIdentityOptions.TenantId = "...";
-   })
+   .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
-   .AddDownstreamApi("DownstreamApi", configOptions =>
-   {
-       configOptions.BaseUrl = "...";
-       configOptions.Scopes = [ "..." ];
-   })
+   .AddDownstreamApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi"))
    .AddDistributedTokenCaches();

運用アプリでは、運用分散トークン キャッシュ プロバイダーを使用する必要があります。 そうしないと、一部のシナリオでアプリのパフォーマンスが低下する可能性があります。 詳細については、「 運用分散トークン キャッシュ プロバイダーの使用 」セクションを参照してください。

MinimalApiJwt プロジェクトで、次のアプリ設定構成をappsettings.json ファイルに追加します。

"Authentication": {
  "Schemes": {
    "Bearer": {
      "Authority": "https://sts.windows.net/{TENANT ID (WEB API)}/",
      "ValidAudiences": [ "{APP ID URI (WEB API)}" ]
    }
  }
},

上記の構成のプレースホルダーを、 Program ファイルでアプリが使用する値と一致するように更新します。

  • {TENANT ID (WEB API)}: Web API のテナント ID。
  • {APP ID URI (WEB API)}: Web API のアプリ ID URI。

機関の形式には、次のパターンが採用されています。

  • ME-ID テナントの種類: https://sts.windows.net/{TENANT ID}/
  • B2C テナントの種類: https://login.microsoftonline.com/{TENANT ID}/v2.0/

対象ユーザー形式では、次のパターンが採用されます ({CLIENT ID} は Web API のクライアント ID です。 {DIRECTORY NAME} はディレクトリ名です (たとえば、 contoso)。

  • ME-ID テナントの種類: api://{CLIENT ID}
  • B2C テナントの種類: https://{DIRECTORY NAME}.onmicrosoft.com/{CLIENT ID}

構成は、JWT ベアラー認証ビルダーによって自動的に取得されます。

Program ファイルから次の行を削除します。

- jwtOptions.Authority = "...";
- jwtOptions.Audience = "...";

構成の詳細については、次のリソースを参照してください。

運用分散トークン キャッシュ プロバイダーを使用する

メモリ内分散トークン キャッシュは、 AddDistributedTokenCaches を呼び出すときに作成され、分散トークン キャッシュに使用できる基本実装があることを確認します。

運用 Web アプリと Web API では、運用分散トークン キャッシュ ( RedisMicrosoft SQL ServerMicrosoft Azure Cosmos DB など) を使用する必要があります。

1 台のコンピューターでのローカル開発とテストでは、分散トークン キャッシュの代わりにメモリ内トークン キャッシュを使用できます。

builder.Services.AddInMemoryTokenCaches();

開発とテストの期間の後半で、運用分散トークン キャッシュ プロバイダーを採用します。

AddDistributedMemoryCache は、キャッシュ項目をメモリに格納する IDistributedCache の既定の実装を追加します。これは、トークン キャッシュのために Microsoft Identity Web によって使用されます。

分散トークン キャッシュは、 MsalDistributedTokenCacheAdapterOptionsによって構成されます。

  • デバッグを目的とした開発では、 DisableL1Cachetrue に設定することで、L1 キャッシュを無効にすることができます。 運用環境では、必ず false にリセットしてください。
  • L1CacheOptions.SizeLimitを使用して L1 キャッシュの最大サイズを設定して、キャッシュがサーバーのメモリをオーバーランしないようにします。 既定値は 500 MB です。
  • デバッグ目的での開発では、 Encryptfalse (既定値) に設定することで、保存時のトークン暗号化を無効にすることができます。 運用環境では、必ず true にリセットしてください。
  • SlidingExpirationを使用してキャッシュからトークンの削除を設定します。 既定値は 1 時間です。
  • L2 キャッシュ エラー (OnL2CacheFailure) のコールバックと非同期の L2 キャッシュ書き込み (EnableAsyncL2Write) に関するガイダンスなど、詳細については、「 MsalDistributedTokenCacheAdapterOptionsトークン キャッシュのシリアル化: 分散トークン キャッシュ」を参照してください。
builder.Services.AddDistributedMemoryCache();

builder.Services.Configure<MsalDistributedTokenCacheAdapterOptions>(
    options => 
    {
      // The following lines that are commented out reflect
      // default values. We recommend overriding the default
      // value of Encrypt to encrypt tokens at rest.

      //options.DisableL1Cache = false;
      //options.L1CacheOptions.SizeLimit = 500 * 1024 * 1024;
      options.Encrypt = true;
      //options.SlidingExpiration = TimeSpan.FromHours(1);
    });

AddDistributedMemoryCache には、 Microsoft.Extensions.Caching.Memory NuGet パッケージへのパッケージ参照が必要です。

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

運用分散キャッシュ プロバイダーを構成するには、 ASP.NET Core での分散キャッシュに関するページを参照してください。

警告

運用環境にアプリをデプロイするときは、常にメモリ内分散トークン キャッシュを実際のトークン キャッシュ プロバイダーに置き換えます。 運用分散トークン キャッシュ プロバイダーを採用できない場合、アプリのパフォーマンスが大幅に低下する可能性があります。

詳細については、「 トークン キャッシュのシリアル化: 分散キャッシュ」を参照してください。 ただし、示されているコード例は、AddDistributedMemoryCacheではなく、AddDistributedTokenCache経由で分散キャッシュを構成する ASP.NET Core アプリには適用されません。

運用環境で共有 Data Protection キー リングを使用すると、Web ファーム内のサーバー間でアプリのインスタンスが、 MsalDistributedTokenCacheAdapterOptions.Encrypttrue に設定されているときにトークンを復号化できます。

1 台のコンピューターでの早期開発とローカル テストでは、 Encryptfalse に設定し、後で共有 Data Protection キー リングを構成できます。

options.Encrypt = false;

開発とテストの期間の後半で、トークン暗号化を有効にし、共有データ保護キー リングを採用します。

次の例は、共有キー リングに Azure Blob Storage と Azure Key Vault (PersistKeysToAzureBlobStorage/ProtectKeysWithAzureKeyVault) を使用する方法を示しています。 サービス構成は、デモンストレーションを目的としたベース ケース シナリオです。 運用アプリをデプロイする前に、Azure サービスを理解し、このセクションの最後に記載されている専用のドキュメント セットを使用してベスト プラクティスを採用してください。

Blazor Web Appのサーバー プロジェクトに次のパッケージがあることを確認します。

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

次の手順に進む前に、アプリが Microsoft Entra に登録されていることを確認します。

通常、次のコードは、 運用分散トークン キャッシュ プロバイダー が実装されるのと同時に実装されます。 Azure 内と Azure の外部の両方の他のオプションは、複数のアプリ インスタンス間で Data Protection キーを管理するために使用できますが、サンプル アプリでは Azure サービスの使用方法を示しています。

データ保護キーを維持し、Azure Key Vault を使用して保存時に暗号化するように Azure Blob Storage を構成します。

  • Azure ストレージ アカウントを作成します。 次の例のアカウント名は contoso

  • データ保護キーを保持するコンテナーを作成します。 次の例のコンテナー名は data-protectionです。

  • ローカル コンピューターにキー ファイルを作成します。 次の例では、キー ファイルの名前は keys.xml です。 テキスト エディターを使用してファイルを作成できます。

    keys.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <repository>
    </repository>
    
  • キー ファイル (keys.xml) をストレージ アカウントのコンテナーにアップロードします。 ポータルのキー行の末尾にあるコンテキスト メニューの [表示/編集] コマンドを使用して、BLOB に上記のコンテンツが含まれていることを確認します。

  • コンテキスト メニューの [SAS の生成 ] コマンドを使用して、SHARED Access Signature (SAS) を使用して BLOB の URI を取得します。 SAS を作成するときは、 ReadAddCreateWriteDeleteのアクセス許可を使用します。 URI は、後で {BLOB URI WITH SAS} プレースホルダーが表示される場所で使用されます。

Entra または Azure portal でキー ボールトを設定する際:

  • ボールト アクセス ポリシーを使用するようにキー ボールトを設定します。 [ネットワーク] ステップでパブリック アクセスが有効になっていることを確認します (オン)。

  • [ アクセス ポリシー ] ウィンドウで、 GetUnwrap Key、および Wrap Key キーのアクセス許可を持つ新しいアクセス ポリシーを作成します。 登録されているアプリケーションをサービス プリンシパルとして選択します。

  • キー暗号化がアクティブな場合、キー ファイル内のキーにコメント "This key is encrypted with Azure Key Vault." が含まれる場合は、アプリを起動した後、キー行の末尾にあるコンテキスト メニューから [表示/編集] コマンドを選択して、キー コンテナーのセキュリティが適用されていることを確認します。

次の例の AzureEventSourceLogForwarder サービスは、ログ記録のために Azure SDK からログ メッセージを転送し、 Microsoft.Extensions.Azure NuGet パッケージを必要とします。

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

Program ファイルの先頭で、Microsoft.Extensions.Azure名前空間の API へのアクセスを指定します。

using Microsoft.Extensions.Azure;

サービスが登録されている Program ファイルで、次のコードを使用します。

builder.Services.TryAddSingleton<AzureEventSourceLogForwarder>();

builder.Services.AddDataProtection()
    .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI WITH SAS}"))
    .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), new DefaultAzureCredential());

{BLOB URI WITH SAS}: クエリ文字列パラメーターとして SAS トークンを使用してキー ファイルを格納する必要がある完全な URI。 URI は、アップロードされたキー ファイルの SAS を要求すると、Azure Storage によって生成されます。 次の例のコンテナー名は data-protectionで、ストレージ アカウント名は contoso。 キー ファイルの名前は keys.xml です。

例:

https://contoso.blob.core.windows.net/data-protection/keys.xml?sp={PERMISSIONS}&st={START DATETIME}&se={EXPIRATION DATETIME}&spr=https&sv={STORAGE VERSION DATE}&sr=c&sig={TOKEN}

{KEY IDENTIFIER}: キー暗号化に使用される Azure Key Vault キー識別子。 次の例では、キー コンテナー名が contoso され、アクセス ポリシーにより、アプリケーションは GetUnwrap Key、および Wrap Key のアクセス許可を持つキー コンテナーにアクセスできます。 キー名の例は data-protection。 キーのバージョン ({KEY VERSION} プレースホルダー) は、作成後に Entra または Azure portal のキーから取得されます。

例:

https://contoso.vault.azure.net/keys/data-protection/{KEY VERSION}

または、JSON 構成プロバイダーを使用して、アプリ設定ファイルから値を指定するようにアプリを構成することもできます。 アプリ設定ファイルに次を追加します。

"DistributedTokenCache": {
    "DisableL1Cache": false,
    "L1CacheSizeLimit": 524288000,
    "Encrypt": true,
    "SlidingExpirationInHours": 1
  },
"DataProtection": {
  "BlobUriWithSasToken": "{BLOB URI WITH SAS}",
  "KeyIdentifier": "{KEY IDENTIFIER}"
}

Program ファイルを次のように変更します。

builder.Services.Configure<MsalDistributedTokenCacheAdapterOptions>(
    options =>
    {
+       var config = builder.Configuration.GetSection("DistributedTokenCache");

-       options.DisableL1Cache = false;
+       options.DisableL1Cache = config.GetValue<bool>("DisableL1Cache");

-       options.L1CacheOptions.SizeLimit = 500 * 1024 * 1024;
+       options.L1CacheOptions.SizeLimit = config.GetValue<long>("L1CacheSizeLimit");

-       options.Encrypt = true;
+       options.Encrypt = config.GetValue<bool>("Encrypt");

-       options.SlidingExpiration = TimeSpan.FromHours(1);
+       options.SlidingExpiration = 
+           TimeSpan.FromHours(config.GetValue<int>("SlidingExpirationInHours"));
    });

- builder.Services.AddDataProtection()
-     .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI WITH SAS}"))
-     .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), new DefaultAzureCredential());

Program ファイルにサービスが構成されている次のコードを追加します。

var config = builder.Configuration.GetSection("DataProtection");

builder.Services.AddDataProtection()
    .PersistKeysToAzureBlobStorage(
        new Uri(config.GetValue<string>("BlobUriWithSasToken") ??
        throw new Exception("Missing Blob URI")))
    .ProtectKeysWithAzureKeyVault(
        new Uri(config.GetValue<string>("KeyIdentifier") ?? 
        throw new Exception("Missing Key Identifier")), 
        new DefaultAzureCredential());

共有データ保護キー リングとキー ストレージ プロバイダーの使用の詳細については、次のリソースを参照してください。

ログアウト時にホーム ページにリダイレクトする

LogInOrOut コンポーネント (Layout/LogInOrOut.razor) は、戻り値の URL (ReturnUrl) の非表示フィールドを現在の URL (currentURL) に設定します。 ユーザーがアプリからサインアウトすると、ID プロバイダーはログアウト元のページにユーザーを返します。ユーザーがセキュリティで保護されたページからログアウトすると、同じセキュリティで保護されたページに戻り、認証プロセスを通じて返送されます。 この認証フローは、ユーザーがアカウントを定期的に変更する必要がある場合に妥当です。

または、次の LogInOrOut コンポーネントを使用します。このコンポーネントでは、ログアウト時に戻り URL が提供されません。

Layout/LogInOrOut.razor:

<div class="nav-item px-3">
    <AuthorizeView>
        <Authorized>
            <form action="authentication/logout" method="post">
                <AntiforgeryToken />
                <button type="submit" class="nav-link">
                    <span class="bi bi-arrow-bar-left-nav-menu" aria-hidden="true">
                    </span> Logout
                </button>
            </form>
        </Authorized>
        <NotAuthorized>
            <a class="nav-link" href="authentication/login">
                <span class="bi bi-person-badge-nav-menu" aria-hidden="true"></span>
                Login
            </a>
        </NotAuthorized>
    </AuthorizeView>
</div>

気象データのセキュリティ

このアプリが気象データをセキュリティで保護する方法の詳細については、「 対話型自動レンダリングを使用して Blazor Web Appデータをセキュリティで保護する」を参照してください。

トラブルシューティング

ログの記録

サーバー アプリは標準の ASP.NET Core アプリです。 サーバー アプリでログ 記録レベルを下げるには、 ASP.NET Core のログ記録に関するガイダンスを参照してください。

Blazor WebAssembly認証のデバッグまたはトレースログを有効にするには、記事バージョンセレクターを.NET 7 以降のASP.NET Coreに設定して、のBlazorのセクションを参照してください。

一般的なエラー

  • Microsoft Entra 外部 ID を使用してログアウト中にデバッガーが例外で中断する

    次の例外は、 Microsoft Entra External ID を使用したログアウト中に Visual Studio デバッガーを停止します。

    Uncaught TypeError TypeError: Failed to execute 'postMessage' on 'Window': The provided value cannot be converted to a sequence.

    ログアウト中の JavaScript 例外による Visual Studio デバッガーの停止

    例外は Entra JavaScript コードからスローされるため、これは ASP.NET Core の問題ではありません。 この例外は運用環境のアプリ機能に影響しないため、ローカル開発テスト中に例外を無視できます。

  • アプリまたは Identity プロバイダー (IP) の構成の誤り

    最も一般的なエラーの原因は、構成の誤りです。 以下に例を示します。

    • シナリオの要件によっては、権限、インスタンス、テナント ID、テナント ドメイン、クライアント ID、またはリダイレクト URI の欠落または誤りによって、アプリによるクライアントの認証ができなくなります。
    • 要求スコープが正しくないと、クライアントはサーバー Web API エンドポイントにアクセスできません。
    • サーバー API のアクセス許可が正しくないか、存在しないと、クライアントがサーバー Web API エンドポイントにアクセスできなくなります。
    • IP のアプリ登録のリダイレクト URI で構成されているものとは異なるポートでアプリが実行されています。 Microsoft Entra ID と、localhost 開発テスト アドレスで実行されるアプリにポートは必要ありませんが、アプリのポート構成とアプリが実行されているポートは、localhost 以外のアドレスと一致する必要があることに注意してください。

    この記事の構成範囲では、正しい構成の例を示しています。 アプリと IP の構成に誤りがないか、構成を慎重に確認してください。

    構成が正しい場合:

    • アプリケーション ログを分析します。

    • ブラウザーの開発者ツールを使用して、クライアント アプリと IP またはサーバー アプリの間のネットワーク トラフィックを確認します。 多くの場合、要求を行った後、IP またはサーバー アプリによって、問題の原因を特定する手掛かりを含む正確なエラー メッセージまたはメッセージがクライアントに返されます。 開発者ツールのガイダンスは、次の記事にあります。

    ドキュメント チームは、ドキュメントのフィードバックや記事のバグ ( このページ のフィードバック セクションから問題を開く) に応答しますが、製品サポートを提供できません。 アプリのトラブルシューティングに役立つ、いくつかのパブリック サポート フォーラムが用意されています。 次をお勧めします。

    上記のフォーラムは、Microsoft によって所有または管理されていません。

    セキュリティ上の問題ではなく、重要でない、機密でない再現可能なフレームワークのバグレポートについては、ASP.NET Core 製品ユニットに問題を報告します。 問題の原因を徹底的に調査し、パブリック サポート フォーラムのコミュニティの助けを借りてもお客様自身で解決できない場合にのみ、製品単位でイシューを作成してください。 単純な構成の誤りやサードパーティのサービスに関連するユース ケースによって破損した個々のアプリのトラブルシューティングは、製品単位で行うことはできません。 レポートが機密性の高い性質のものである場合や、攻撃者が悪用するおそれのある製品の潜在的なセキュリティ上の欠陥が記述されている場合は、「dotnet/aspnetcore」 ( GitHub リポジトリ) をご覧ください。

  • ME-ID で承認されないクライアント

    情報:Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2] 承認に失敗しました。 次の要件が満たされていません。DenyAnonymousAuthorizationRequirement:認証済みユーザーが必要です。

    ME-ID からのログイン コールバック エラー:

    • エラー: unauthorized_client
    • 説明: AADB2C90058: The provided application is not configured to allow public clients.

    このエラーを解決するには:

    1. Azure portal で、 アプリのマニフェストにアクセスします。
    2. allowPublicClient属性nullまたはtrueに設定します。

Cookie とサイト データ

Cookie とサイト データは、アプリが更新されても保持され、テストやトラブルシューティングに影響する可能性があります。 アプリ コードの変更、プロバイダーによるユーザー アカウントの変更、プロバイダー アプリの構成変更を行うときは、次のものをクリアしてください。

  • ユーザーのサインインの Cookie
  • アプリの Cookie
  • キャッシュおよび保存されたサイト データ

残った Cookie とサイト データがテストとトラブルシューティングに影響しないようにする方法を、次に示します。

  • ブラウザーを構成する
    • ブラウザーが閉じるたびに cookie とサイト データをすべて削除するように構成できることをテストするために、ブラウザーを使用します。
    • アプリ、テスト ユーザー、プロバイダー構成が変更されるたびにブラウザーが手動で、または IDE によって閉じられていることを確認します。
  • カスタム コマンドを使用して、Visual Studio でブラウザーを InPrivate または Incognito モードで開きます:
    • Visual Studio の [実行] ボタンから [一覧で参照] ダイアログ ボックスを開きます。
    • [ 追加 ] ボタンを選択します。
    • [ プログラム ] フィールドにブラウザーへのパスを指定します。 次の実行可能パスが、Windows 10 の一般的なインストール場所です。 ブラウザーが別の場所にインストールされている場合、または Windows 10 を使用していない場合は、ブラウザーの実行可能ファイルのパスを指定してください。
      • Microsoft Edge: C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe
      • Google Chrome: C:\Program Files (x86)\Google\Chrome\Application\chrome.exe
      • Mozilla Firefox: C:\Program Files\Mozilla Firefox\firefox.exe
    • [ 引数 ] フィールドで、ブラウザーが InPrivate または Incognito モードで開くために使用するコマンド ライン オプションを指定します。 ブラウザーによっては、アプリの URL が必要になる場合があります。
      • Microsoft Edge:-inprivate を使用してください。
      • Google Chrome: --incognito --new-window {URL} を使用します。プレースホルダー {URL} は開く URL (たとえば、https://localhost:5001 など) です。
      • Mozilla Firefox: -private -url {URL} を使用します。プレースホルダー {URL} は開く URL (たとえば、https://localhost:5001 など) です。
    • わかりやすい名前フィールドに名前を指定します。 たとえば、Firefox Auth Testing のようにします。
    • [ OK ] ボタンを選択します。
    • アプリを使用してテストを繰り返すたびにブラウザー プロファイルを選択する必要がないようにするには、[既定に設定] ボタンを使用してプロファイルを既定値 として設定 します。
    • アプリ、テスト ユーザー、またはプロバイダー構成が変更されるたびに、ブラウザーが IDE によって閉じられていることを確認します。

アプリのアップグレード

開発マシンで .NET Core SDK をアップグレードしたり、アプリ内のパッケージ バージョンを変更したりした直後に、機能しているアプリが失敗することがあります。 場合によっては、パッケージに統一性がないと、メジャー アップグレード実行時にアプリが破壊されることがあります。 これらの問題のほとんどは、次の手順で解決できます。

  1. コマンド シェルから dotnet nuget locals all --clear を実行して、ローカル システムの NuGet パッケージ キャッシュをクリアします。
  2. プロジェクトのフォルダー binobj を削除します。
  3. プロジェクトを復元してリビルドします。
  4. アプリを再展開する前に、サーバー上の展開フォルダー内のすべてのファイルを削除します。

アプリのターゲット フレームワークと互換性のないパッケージ バージョンの使用はサポートされていません。 パッケージの詳細については、 NuGet ギャラリーを使用します。

適切なプロジェクトからソリューションを開始する

Blazor Web Apps:

  • Backend-for-Frontend (BFF) パターン サンプルの 1 つについては、 Aspire/Aspire.AppHost プロジェクトからソリューションを開始します。
  • BFF 以外のパターン サンプルの 1 つについては、 サーバー プロジェクトからソリューションを開始します。

Blazor Server:

サーバー プロジェクトからソリューションを開始します。

ユーザーを検査する

次の UserClaims コンポーネントは、アプリ内で直接使うことも、さらにカスタマイズするための基礎として使うこともできます。

UserClaims.razor:

@page "/user-claims"
@using System.Security.Claims
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]

<PageTitle>User Claims</PageTitle>

<h1>User Claims</h1>

@if (claims.Any())
{
    <ul>
        @foreach (var claim in claims)
        {
            <li><b>@claim.Type:</b> @claim.Value</li>
        }
    </ul>
}

@code {
    private IEnumerable<Claim> claims = Enumerable.Empty<Claim>();

    [CascadingParameter]
    private Task<AuthenticationState>? AuthState { get; set; }

    protected override async Task OnInitializedAsync()
    {
        if (AuthState == null)
        {
            return;
        }

        var authState = await AuthState;
        claims = authState.User.Claims;
    }
}

その他のリソース