次の方法で共有


OpenID Connect (OIDC) を使用して ASP.NET Core Blazor Web App をセキュリティで保護する

Note

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

重要

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

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

この記事では、Blazor Web App GitHub リポジトリ (.NET 8 以降) のサンプル アプリを使用して、dotnet/blazor-samples をセキュリティ保護する方法について説明します (ダウンロード方法)。

このバージョンの記事では、Backend for Frontend (BFF) パターンを採用せずに OIDC を実装する方法について説明します。 BFF パターンは、外部サービスに対して認証された要求を行う場合に役立ちます。 アプリの仕様上 BFF パターンの採用が必要な場合は、記事のバージョン セレクターを OIDC と BFF パターンに切り替えてください。

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

  • Blazor Web App は、グローバル インタラクティビティを持つ自動レンダー モードを使用します。
  • ユーザーの認証状態をキャプチャし、それをサーバーとクライアントの間でフローするために、サーバーおよびクライアント アプリによってカスタム認証状態プロバイダー サービスが使用されます。
  • このアプリは、すべての OIDC 認証フローの開始点です。 OIDC は、アプリ内で手動により構成され、Microsoft Entra IDMicrosoft Identity Web パッケージに依存せず、サンプル アプリも Microsoft Azure ホスティングを必要としません。 ただし、サンプル アプリは Entra や Microsoft Identity Web で使用でき、Azure でホストできます。
  • 非対話形式でのトークンの自動更新。
  • データのサーバー プロジェクトで (Web) API を安全に呼び出します。

サンプル アプリ

サンプル アプリは、以下の 2 つのプロジェクトで構成されます。

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

次のリンクを使用して、リポジトリのルートから最新バージョンのフォルダーを介してサンプル アプリにアクセスします。 これらのプロジェクトは、.NET 8 以降用の BlazorWebAppOidc フォルダー内にあります。

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

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

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

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

Note

サーバー プロジェクトは IHttpContextAccessor/HttpContext を使いますが、対話形式でレンダリングされるコンポーネントには使いません。 詳細については、対話型サーバー側の ASP.NET Core Blazor レンダリングの脅威軽減策に関するガイダンスに関する記事を参照してください。

構成

このセクションでは、サンプル アプリを構成する方法について説明します。

Note

Microsoft Entra ID または Azure AD B2C の場合は、OIDC と AddMicrosoftIdentityWebApp 認証ハンドラーの両方を適切な既定値で追加する Microsoft Identity Web (Microsoft.Identity.Web NuGet パッケージAPI ドキュメント) のCookieを使用できます。 このセクションのサンプル アプリとガイダンスでは、Microsoft Identity Web は使用しません。 このガイダンスでは、任意の OIDC プロバイダー用の OIDC ハンドラーを "手動で" 構成する方法を示します。 Microsoft Identity Web の実装の詳細については、リンクされているリソースを参照してください。

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

警告

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

ローカル開発テストの場合は、 Secret Manager ツール を使用して、サーバー アプリのクライアント シークレットを構成キー Authentication:Schemes:MicrosoftOidc:ClientSecretの下に格納します。

Note

アプリで Microsoft Entra ID または Azure AD B2C を使用する場合は、Entra または Azure portal でアプリの登録にクライアント シークレットを作成します (Manage>Certificates & secrets>New クライアント シークレット)。 次のガイダンスでは、新しいシークレットの Value を使用します。

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

dotnet user-secrets init

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

dotnet user-secrets set "Authentication:Schemes:MicrosoftOidc:ClientSecret" "{SECRET}"

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

Configure the app

次の OpenIdConnectOptions の構成は、プロジェクトの Program ファイルに含まれる AddOpenIdConnect の呼び出しにあります。

  • SignInScheme: 認証の成功後に、ユーザーの identity の保持を担当するミドルウェア対応の認証スキームを設定します。 OIDC ハンドラーでは、複数の要求にわたってユーザー資格情報を保持できるサインイン スキームを使用する必要があります。 次の行は、デモンストレーションの目的のためだけに存在します。 省略すると、DefaultSignInScheme がフォールバック値として使用されます。

    oidcOptions.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    
  • openidprofile のスコープ (Scope) (省略可能): openidprofile スコープも、OIDC ハンドラーが機能するために必要なので既定で構成されますが、スコープが Authentication:Schemes:MicrosoftOidc:Scope の構成に含まれている場合は、これらを追加し直すことが必要になる場合があります。 一般的な構成のガイダンスについては、「ASP.NET Core の構成」と「ASP.NET Core Blazor の構成」を参照してください。

    oidcOptions.Scope.Add(OpenIdConnectScope.OpenIdProfile);
    
  • SaveTokens: 認可が成功した後で、アクセス トークンと更新トークンを AuthenticationProperties 内に保存する必要があるかどうかを定義します。 このプロパティは、最終的な認証 false のサイズを小さくするために、cookie に設定されます。

    oidcOptions.SaveTokens = false;
    
  • オフライン アクセスのスコープ (Scope): offline_access スコープが更新トークンのために必要です。

    oidcOptions.Scope.Add(OpenIdConnectScope.OfflineAccess);
    
  • AuthorityClientId: OIDC 呼び出しのためのオーソリティとクライアント ID を設定します。

    oidcOptions.Authority = "{AUTHORITY}";
    oidcOptions.ClientId = "{CLIENT ID}";
    

    例:

    • オーソリティ ({AUTHORITY}): https://login.microsoftonline.com/aaaabbbb-0000-cccc-1111-dddd2222eeee/v2.0/ (テナント ID aaaabbbb-0000-cccc-1111-dddd2222eeee を使用)
    • クライアント ID ({CLIENT ID}): 00001111-aaaa-2222-bbbb-3333cccc4444
    oidcOptions.Authority = "https://login.microsoftonline.com/aaaabbbb-0000-cccc-1111-dddd2222eeee/v2.0/";
    oidcOptions.ClientId = "00001111-aaaa-2222-bbbb-3333cccc4444";
    

    Microsoft Azure の "common" オーソリティの例:

    "common" オーソリティは、マルチテナント アプリに対して使用する必要があります。 "common" オーソリティをシングルテナント アプリに対して使用することもできますが、このセクションで後ほど示すように、カスタムの IssuerValidator が必要になります。

    oidcOptions.Authority = "https://login.microsoftonline.com/common/v2.0/";
    
  • ResponseType: 認可コード フローのみを実行するように、OIDC ハンドラーを構成します。 このモードでは、暗黙的な許可とハイブリッド フローは必要ありません。

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

    oidcOptions.ResponseType = OpenIdConnectResponseType.Code;
    
  • MapInboundClaims および NameClaimTypeRoleClaimType の構成: 多くの OIDC サーバーは、name の既定値 SOAP/WS-Fed ではなく、"role" と "ClaimTypes" を使用します。 MapInboundClaimsfalse に設定されていると、ハンドラーは要求のマッピングを実行せず、JWT からの要求名がアプリによって直接使用されます。 次の例では、ロール要求の種類を "roles" に設定します。これは、Microsoft Entra ID (ME-ID) に適しています。 詳細については、お使いの identity プロバイダーのドキュメントを調べてください。

    Note

    ほとんどの OIDC プロバイダーでは、MapInboundClaimsfalse に設定する必要があります。こうすることで、要求の名前は変更されなくなります。

    oidcOptions.MapInboundClaims = false;
    oidcOptions.TokenValidationParameters.NameClaimType = "name";
    oidcOptions.TokenValidationParameters.RoleClaimType = "roles";
    
  • パスの構成: パスは、OIDC プロバイダーにアプリケーションを登録するときに構成された、リダイレクト URI (ログイン コールバック パス) およびログアウト後リダイレクト (サインアウト コールバック パス) パスと、一致している必要があります。 Azure portal では、パスはアプリの登録の [認証] ブレードで構成されます。 サインインとサインアウト両方のパスを、リダイレクト URI として登録する必要があります。 既定値は、/signin-oidc/signout-callback-oidc です。

    • CallbackPath: ユーザーエージェントが返される、アプリのベース パス内の要求パス。

      Entra または Azure portal で、以下のように Web プラットフォーム構成のリダイレクト URI 内のパスを設定します。

      https://localhost/signin-oidc

      Note

      Microsoft Entra ID を使う場合、localhost アドレスにポートは必要ありません。 他のほとんどの OIDC プロバイダーでは、正しいポートが必要です。

    • SignedOutCallbackPath: アプリのベース パス内の要求パス。identity プロバイダーからサインアウトした後でユーザー エージェントが返される。

      Entra または Azure portal で、以下のように Web プラットフォーム構成のリダイレクト URI 内のパスを設定します。

      https://localhost/signout-callback-oidc

      Note

      Microsoft Entra ID を使う場合、localhost アドレスにポートは必要ありません。 他のほとんどの OIDC プロバイダーでは、正しいポートが必要です。

      Note

      Microsoft Identity Web を使用していて、SignedOutCallbackPath オーソリティ (microsoftonline.com) が使用されている場合、プロバイダーは現在、https://login.microsoftonline.com/{TENANT ID}/v2.0/ にのみリダイレクトを行います。 Microsoft Identity Web で "common" オーソリティを使用できる場合は、この制限は存在しません。 詳細については、「オーソリティ URL にテナント ID が含まれていると postLogoutRedirectUri が機能しない (AzureAD/microsoft-authentication-library-for-js #5783)」を参照してください。

    • RemoteSignOutPath: このパスで要求を受け取ると、ハンドラーはサインアウト スキームを使用してサインアウトを呼び出します。

      Entra または Azure portal で、以下のようにフロントチャネル ログアウト URL を設定します。

      https://localhost/signout-oidc

      Note

      Microsoft Entra ID を使う場合、localhost アドレスにポートは必要ありません。 他のほとんどの OIDC プロバイダーでは、正しいポートが必要です。

      oidcOptions.CallbackPath = new PathString("{PATH}");
      oidcOptions.SignedOutCallbackPath = new PathString("{PATH}");
      oidcOptions.RemoteSignOutPath = new PathString("{PATH}");
      

      例 (既定値):

      oidcOptions.CallbackPath = new PathString("/signin-oidc");
      oidcOptions.SignedOutCallbackPath = new PathString("/signout-callback-oidc");
      oidcOptions.RemoteSignOutPath = new PathString("/signout-oidc");
      
  • ("common" エンドポイントを使用する Microsoft Azure のみ) : 多くの OIDC プロバイダーは既定の発行者検証コントロールで動作しますが、TokenValidationParameters.IssuerValidator によって返されるテナント ID ({TENANT ID}) でパラメーター化された発行者を考慮する必要があります。https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration 詳細については、「OpenID Connect と Azure AD の "common" エンドポイントでの SecurityTokenInvalidIssuerException (AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet #1731)」を参照してください。

    Microsoft Entra ID または Azure AD B2C と "common" エンドポイントを使用するアプリの場合のみ:

    var microsoftIssuerValidator = AadIssuerValidator.GetAadIssuerValidator(oidcOptions.Authority);
    oidcOptions.TokenValidationParameters.IssuerValidator = microsoftIssuerValidator.Validate;
    

サンプル アプリ コード

サンプル アプリで以下の機能を調べます。

  • カスタム cookie リフレッシャー (CookieOidcRefresher.cs) を使用する非対話形式でのトークンの自動更新。
  • サーバー プロジェクトは AddAuthenticationStateSerialization を呼び出して、 PersistentComponentState を使用して認証状態をクライアントにフローするサーバー側認証状態プロバイダーを追加します。 クライアントは AddAuthenticationStateDeserialization を呼び出して、サーバーによって渡された認証状態を逆シリアル化して使用します。 認証状態は、WebAssembly アプリケーションの有効期間中は変わりません。
  • Blazor Web App への気象データの要求の例は、/weather-forecast ファイル (Program) の Minimal API エンドポイント (Program.cs) によって処理されます。 このエンドポイントは、RequireAuthorization を呼び出すことで認可を要求します。 プロジェクトに追加するすべてのコントローラーについて、[Authorize] 属性をコントローラーまたはアクションに追加します。
  • アプリは、気象データのサーバー プロジェクトで (Web) API を安全に呼び出します。
    • Weather コンポーネントをサーバーでレンダリングする場合、コンポーネントはサーバーで ServerWeatherForecaster を使用して気象データを直接取得します (Web API 呼び出しを使用しません)。
    • コンポーネントをクライアントでレンダリングする場合、コンポーネントは ClientWeatherForecaster サービス実装を使用します。この実装では、事前に構成した HttpClient (クライアント プロジェクトの Program ファイル内) を使用して、サーバー プロジェクトへの Web API 呼び出しが行われます。 サーバー プロジェクトの /weather-forecast ファイルで定義されている最小 API エンドポイント (Program) は、ServerWeatherForecaster から気象データを取得し、そのデータをクライアントに返します。
  • カスタム cookie リフレッシャー (CookieOidcRefresher.cs) を使用する非対話形式でのトークンの自動更新。
  • PersistingAuthenticationStateProvider クラス (PersistingAuthenticationStateProvider.cs) は、AuthenticationStateProvider を使用してクライアントに認証状態をフローするサーバー側 PersistentComponentState であり、WebAssembly アプリケーションの有効期間中は固定されます。
  • Blazor Web App への気象データの要求の例は、/weather-forecast ファイル (Program) の Minimal API エンドポイント (Program.cs) によって処理されます。 このエンドポイントは、RequireAuthorization を呼び出すことで認可を要求します。 プロジェクトに追加するすべてのコントローラーについて、[Authorize] 属性をコントローラーまたはアクションに追加します。
  • アプリは、気象データのサーバー プロジェクトで (Web) API を安全に呼び出します。
    • Weather コンポーネントをサーバーでレンダリングする場合、コンポーネントはサーバーで ServerWeatherForecaster を使用して気象データを直接取得します (Web API 呼び出しを使用しません)。
    • コンポーネントをクライアントでレンダリングする場合、コンポーネントは ClientWeatherForecaster サービス実装を使用します。この実装では、事前に構成した HttpClient (クライアント プロジェクトの Program ファイル内) を使用して、サーバー プロジェクトへの Web API 呼び出しが行われます。 サーバー プロジェクトの /weather-forecast ファイルで定義されている最小 API エンドポイント (Program) は、ServerWeatherForecaster から気象データを取得し、そのデータをクライアントに返します。

Blazor Web App のサービス抽象化を利用した (Web) API 呼び出しの詳細については、ASP.NET Core Blazor アプリからの Web API の呼び出しに関するページをご覧ください。

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

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

クライアントは AddAuthenticationStateDeserialization を呼び出して、サーバーによって渡された認証状態を逆シリアル化して使用します。 認証状態は、WebAssembly アプリケーションの有効期間中は変わりません。

PersistentAuthenticationStateProvider クラス (PersistentAuthenticationStateProvider.cs) は、ページがサーバーでレンダリングされたときにページに保持されていたデータを探すことでユーザーの認証状態を決定するクライアント側 AuthenticationStateProvider です。 認証状態は、WebAssembly アプリケーションの有効期間中は変わりません。

ユーザーがログインまたはログアウトする必要がある場合は、ページ全体の再読み込みが必要です。

サンプル アプリでは、表示のためにユーザー名とメールアドレスだけを提供します。 以降の要求を行うときにサーバーに対する認証を行うトークンは含まれません。これは、サーバーへの cookie 要求に含まれる HttpClient を使用して別に処理されます。

このバージョンの記事では、Backend for Frontend (BFF) パターンを使用して OIDC を実装する方法について説明します。 アプリの仕様上 BFF パターンを採用する必要がない場合は、記事のバージョン セレクターを BFF パターンを使用しない OIDC に変更してください。

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

  • Blazor Web App は、グローバル インタラクティビティを持つ自動レンダー モードを使用します。
  • ユーザーの認証状態をキャプチャし、それをサーバーとクライアントの間でフローするために、サーバーおよびクライアント アプリによってカスタム認証状態プロバイダー サービスが使用されます。
  • このアプリは、すべての OIDC 認証フローの開始点です。 OIDC は、アプリ内で手動により構成され、Microsoft Entra IDMicrosoft Identity Web パッケージに依存せず、サンプル アプリも Microsoft Azure ホスティングを必要としません。 ただし、サンプル アプリは Entra や Microsoft Identity Web で使用でき、Azure でホストできます。
  • 非対話形式でのトークンの自動更新。
  • Backend for Frontend (BFF) パターンは、サービス検出のために .NET Aspire を使用し、要求をバックエンド アプリの天気予報エンドポイントにプロキシするために YARP を使用することで実現されます。
    • バックエンド Web API は、JWT ベアラー認証を使用して、サインイン Blazor Web App の cookie によって保存された JWT トークンを検証します。
    • Aspire は、.NET クラウドネイティブ アプリを構築するエクスペリエンスを強化します。 これは、分散型アプリを構築して実行するための、一貫性のある、厳格なツールとパターンのセットを提供します。
    • YARP (Yet Another Reverse Proxy) は、リバース プロキシ サーバーの作成に使用されるライブラリです。

.NET Aspire の詳細については、「.NET Aspire の一般提供: .NET クラウドネイティブ開発の簡略化 (2024 年 5 月)」をご覧ください。

前提条件

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

サンプル アプリ

サンプル アプリは、以下の 5 つのプロジェクトで構成されます。

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

次のリンクを使用して、リポジトリのルートから最新バージョンのフォルダーを介してサンプル アプリにアクセスします。 これらのプロジェクトは、.NET 8 以降用の BlazorWebAppOidcBff フォルダー内にあります。

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

.NET Aspire 件のプロジェクト

.NET Aspire の使用方法の詳細と、サンプル アプリの .AppHost.ServiceDefaults プロジェクトの詳細については、「.NET Aspire のドキュメント」をご覧ください。

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

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

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

BlazorWebAppOidc プロジェクトは、Blazor Web App のサーバー側プロジェクトです。 このプロジェクトでは、バックエンド Web API プロジェクト () の天気予報エンドポイントに要求をプロキシするための MinimalApiJwt と、認証 access_token 内に保存された cookie を使用します。

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

Note

サーバー プロジェクトは IHttpContextAccessor/HttpContext を使いますが、対話形式でレンダリングされるコンポーネントには使いません。 詳細については、対話型サーバー側の ASP.NET Core Blazor レンダリングの脅威軽減策に関するガイダンスに関する記事を参照してください。

構成

このセクションでは、サンプル アプリを構成する方法について説明します。

Note

Microsoft Entra ID または Azure AD B2C の場合は、OIDC と AddMicrosoftIdentityWebApp 認証ハンドラーの両方を適切な既定値で追加する Microsoft Identity Web (Microsoft.Identity.Web NuGet パッケージAPI ドキュメント) のCookieを使用できます。 このセクションのサンプル アプリとガイダンスでは、Microsoft Identity Web は使用しません。 このガイダンスでは、任意の OIDC プロバイダー用の OIDC ハンドラーを "手動で" 構成する方法を示します。 Microsoft Identity Web の実装の詳細については、リンクされているリソースを参照してください。

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

警告

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

ローカル開発テストの場合は、 Secret Manager ツール を使用して、サーバー アプリのクライアント シークレットを構成キー Authentication:Schemes:MicrosoftOidc:ClientSecretの下に格納します。

Note

アプリで Microsoft Entra ID または Azure AD B2C を使用する場合は、Entra または Azure portal でアプリの登録にクライアント シークレットを作成します (Manage>Certificates & secrets>New クライアント シークレット)。 次のガイダンスでは、新しいシークレットの Value を使用します。

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

dotnet user-secrets init

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

dotnet user-secrets set "Authentication:Schemes:MicrosoftOidc:ClientSecret" "{SECRET}"

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

Configure the app

次の OpenIdConnectOptions の構成は、プロジェクトの Program ファイルに含まれる AddOpenIdConnect の呼び出しにあります。

  • SignInScheme: 認証の成功後に、ユーザーの identity の保持を担当するミドルウェア対応の認証スキームを設定します。 OIDC ハンドラーでは、複数の要求にわたってユーザー資格情報を保持できるサインイン スキームを使用する必要があります。 次の行は、デモンストレーションの目的のためだけに存在します。 省略すると、DefaultSignInScheme がフォールバック値として使用されます。

    oidcOptions.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    
  • openidprofile のスコープ (Scope) (省略可能): openidprofile スコープも、OIDC ハンドラーが機能するために必要なので既定で構成されますが、スコープが Authentication:Schemes:MicrosoftOidc:Scope の構成に含まれている場合は、これらを追加し直すことが必要になる場合があります。 一般的な構成のガイダンスについては、「ASP.NET Core の構成」と「ASP.NET Core Blazor の構成」を参照してください。

    oidcOptions.Scope.Add(OpenIdConnectScope.OpenIdProfile);
    
  • SaveTokens: 認可が成功した後で、アクセス トークンと更新トークンを AuthenticationProperties 内に保存する必要があるかどうかを定義します。 この値は、バックエンド Web API プロジェクト (true) からの気象データの要求を認証するために MinimalApiJwt に設定されます。

    oidcOptions.SaveTokens = true;
    
  • オフライン アクセスのスコープ (Scope): offline_access スコープが更新トークンのために必要です。

    oidcOptions.Scope.Add(OpenIdConnectScope.OfflineAccess);
    
  • Web API から気象データを取得するためのスコープ (Scope): Weather.Get スコープは、Azure portal または Entra ポータルの [APIの公開] で構成します。 これは、バックエンド Web API プロジェクト (MinimalApiJwt) がベアラー JWT でアクセス トークンを検証するために必要です。

    oidcOptions.Scope.Add("{APP ID URI}/{API NAME}");
    

    例:

    • アプリ ID URI ({APP ID URI}): https://{DIRECTORY NAME}.onmicrosoft.com/{CLIENT ID}
      • ディレクトリ名 ({DIRECTORY NAME}): contoso
      • アプリケーション (クライアント) ID ({CLIENT ID}): 00001111-aaaa-2222-bbbb-3333cccc4444
    • MinimalApiJwt ({API NAME}) からの気象データ用に構成されたスコープ: Weather.Get
    oidcOptions.Scope.Add("https://contoso.onmicrosoft.com/00001111-aaaa-2222-bbbb-3333cccc4444/Weather.Get");
    

    前の例は、種類が AAD B2C テナントであるテナントに登録されたアプリに関するものです。 アプリが ME-ID テナントに登録されている場合、アプリ ID URI が異なるため、スコープも異なります。

    例:

    • アプリ ID URI ({APP ID URI}): api://{CLIENT ID}、アプリケーション (クライアント) ID ({CLIENT ID}): 00001111-aaaa-2222-bbbb-3333cccc4444
    • MinimalApiJwt ({API NAME}) からの気象データ用に構成されたスコープ: Weather.Get
    oidcOptions.Scope.Add("api://00001111-aaaa-2222-bbbb-3333cccc4444/Weather.Get");
    
  • AuthorityClientId: OIDC 呼び出しのためのオーソリティとクライアント ID を設定します。

    oidcOptions.Authority = "{AUTHORITY}";
    oidcOptions.ClientId = "{CLIENT ID}";
    

    例:

    • オーソリティ ({AUTHORITY}): https://login.microsoftonline.com/aaaabbbb-0000-cccc-1111-dddd2222eeee/v2.0/ (テナント ID aaaabbbb-0000-cccc-1111-dddd2222eeee を使用)
    • クライアント ID ({CLIENT ID}): 00001111-aaaa-2222-bbbb-3333cccc4444
    oidcOptions.Authority = "https://login.microsoftonline.com/aaaabbbb-0000-cccc-1111-dddd2222eeee/v2.0/";
    oidcOptions.ClientId = "00001111-aaaa-2222-bbbb-3333cccc4444";
    

    Microsoft Azure の "common" オーソリティの例:

    "common" オーソリティは、マルチテナント アプリに対して使用する必要があります。 "common" オーソリティをシングルテナント アプリに対して使用することもできますが、このセクションで後ほど示すように、カスタムの IssuerValidator が必要になります。

    oidcOptions.Authority = "https://login.microsoftonline.com/common/v2.0/";
    
  • ResponseType: 認可コード フローのみを実行するように、OIDC ハンドラーを構成します。 このモードでは、暗黙的な許可とハイブリッド フローは必要ありません。

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

    oidcOptions.ResponseType = OpenIdConnectResponseType.Code;
    
  • MapInboundClaims および NameClaimTypeRoleClaimType の構成: 多くの OIDC サーバーは、name の既定値 SOAP/WS-Fed ではなく、"role" と "ClaimTypes" を使用します。 MapInboundClaimsfalse に設定されていると、ハンドラーは要求のマッピングを実行せず、JWT からの要求名がアプリによって直接使用されます。 次の例では、ロール要求の種類を "roles" に設定します。これは、Microsoft Entra ID (ME-ID) に適しています。 詳細については、お使いの identity プロバイダーのドキュメントを調べてください。

    Note

    ほとんどの OIDC プロバイダーでは、MapInboundClaimsfalse に設定する必要があります。こうすることで、要求の名前は変更されなくなります。

    oidcOptions.MapInboundClaims = false;
    oidcOptions.TokenValidationParameters.NameClaimType = "name";
    oidcOptions.TokenValidationParameters.RoleClaimType = "roles";
    
  • パスの構成: パスは、OIDC プロバイダーにアプリケーションを登録するときに構成された、リダイレクト URI (ログイン コールバック パス) およびログアウト後リダイレクト (サインアウト コールバック パス) パスと、一致している必要があります。 Azure portal では、パスはアプリの登録の [認証] ブレードで構成されます。 サインインとサインアウト両方のパスを、リダイレクト URI として登録する必要があります。 既定値は、/signin-oidc/signout-callback-oidc です。

    • CallbackPath: ユーザーエージェントが返される、アプリのベース パス内の要求パス。

      Entra または Azure portal で、以下のように Web プラットフォーム構成のリダイレクト URI 内のパスを設定します。

      https://localhost/signin-oidc

      Note

      localhost アドレスにはポートは必要ありません。

    • SignedOutCallbackPath: アプリのベース パス内の要求パス。identity プロバイダーからサインアウトした後でユーザー エージェントが返される。

      Entra または Azure portal で、以下のように Web プラットフォーム構成のリダイレクト URI 内のパスを設定します。

      https://localhost/signout-callback-oidc

      Note

      localhost アドレスにはポートは必要ありません。

      Note

      Microsoft Identity Web を使用していて、SignedOutCallbackPath オーソリティ (microsoftonline.com) が使用されている場合、プロバイダーは現在、https://login.microsoftonline.com/{TENANT ID}/v2.0/ にのみリダイレクトを行います。 Microsoft Identity Web で "common" オーソリティを使用できる場合は、この制限は存在しません。 詳細については、「オーソリティ URL にテナント ID が含まれていると postLogoutRedirectUri が機能しない (AzureAD/microsoft-authentication-library-for-js #5783)」を参照してください。

    • RemoteSignOutPath: このパスで要求を受け取ると、ハンドラーはサインアウト スキームを使用してサインアウトを呼び出します。

      Entra または Azure portal で、以下のようにフロントチャネル ログアウト URL を設定します。

      https://localhost/signout-oidc

      Note

      localhost アドレスにはポートは必要ありません。

      oidcOptions.CallbackPath = new PathString("{PATH}");
      oidcOptions.SignedOutCallbackPath = new PathString("{PATH}");
      oidcOptions.RemoteSignOutPath = new PathString("{PATH}");
      

      例 (既定値):

      oidcOptions.CallbackPath = new PathString("/signin-oidc");
      oidcOptions.SignedOutCallbackPath = new PathString("/signout-callback-oidc");
      oidcOptions.RemoteSignOutPath = new PathString("/signout-oidc");
      
  • ("common" エンドポイントを使用する Microsoft Azure のみ) : 多くの OIDC プロバイダーは既定の発行者検証コントロールで動作しますが、TokenValidationParameters.IssuerValidator によって返されるテナント ID ({TENANT ID}) でパラメーター化された発行者を考慮する必要があります。https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration 詳細については、「OpenID Connect と Azure AD の "common" エンドポイントでの SecurityTokenInvalidIssuerException (AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet #1731)」を参照してください。

    Microsoft Entra ID または Azure AD B2C と "common" エンドポイントを使用するアプリの場合のみ:

    var microsoftIssuerValidator = AadIssuerValidator.GetAadIssuerValidator(oidcOptions.Authority);
    oidcOptions.TokenValidationParameters.IssuerValidator = microsoftIssuerValidator.Validate;
    

サンプル アプリ コード

サンプル アプリで以下の機能を調べます。

  • カスタム cookie リフレッシャー (CookieOidcRefresher.cs) を使用する非対話形式でのトークンの自動更新。
  • サーバー プロジェクトは AddAuthenticationStateSerialization を呼び出して、 PersistentComponentState を使用して認証状態をクライアントにフローするサーバー側認証状態プロバイダーを追加します。 クライアントは AddAuthenticationStateDeserialization を呼び出して、サーバーによって渡された認証状態を逆シリアル化して使用します。 認証状態は、WebAssembly アプリケーションの有効期間中は変わりません。
  • Blazor Web App への要求は、バックエンド Web API プロジェクト (MinimalApiJwt) にプロキシされます。 MapForwarder ファイル内の Program は、発信要求の既定の構成、カスタマイズされた変換、既定の HTTP クライアントを使用して、指定されたパターンに一致する HTTP 要求の特定の宛先への直接転送を追加します。
    • Weather コンポーネントをサーバーでレンダリングする場合、コンポーネントは ServerWeatherForecaster を使用し、ユーザーのアクセス トークンで気象データの要求をプロキシします。
    • コンポーネントをクライアントでレンダリングする場合、コンポーネントは ClientWeatherForecaster サービス実装を使用します。この実装では、事前に構成した HttpClient (クライアント プロジェクトの Program ファイル内) を使用して、サーバー プロジェクトへの Web API 呼び出しが行われます。 サーバー プロジェクトの /weather-forecast ファイルで定義されている最小 API エンドポイント (Program) は、ユーザーのアクセス トークンで要求を変換し、気象データを取得します。
  • カスタム cookie リフレッシャー (CookieOidcRefresher.cs) を使用する非対話形式でのトークンの自動更新。
  • PersistingAuthenticationStateProvider クラス (PersistingAuthenticationStateProvider.cs) は、AuthenticationStateProvider を使用してクライアントに認証状態をフローするサーバー側 PersistentComponentState であり、WebAssembly アプリケーションの有効期間中は固定されます。
  • Blazor Web App への要求は、バックエンド Web API プロジェクト (MinimalApiJwt) にプロキシされます。 MapForwarder ファイル内の Program は、発信要求の既定の構成、カスタマイズされた変換、既定の HTTP クライアントを使用して、指定されたパターンに一致する HTTP 要求の特定の宛先への直接転送を追加します。
    • Weather コンポーネントをサーバーでレンダリングする場合、コンポーネントは ServerWeatherForecaster を使用し、ユーザーのアクセス トークンで気象データの要求をプロキシします。
    • コンポーネントをクライアントでレンダリングする場合、コンポーネントは ClientWeatherForecaster サービス実装を使用します。この実装では、事前に構成した HttpClient (クライアント プロジェクトの Program ファイル内) を使用して、サーバー プロジェクトへの Web API 呼び出しが行われます。 サーバー プロジェクトの /weather-forecast ファイルで定義されている最小 API エンドポイント (Program) は、ユーザーのアクセス トークンで要求を変換し、気象データを取得します。

Blazor Web App のサービス抽象化を利用した (Web) API 呼び出しの詳細については、ASP.NET Core Blazor アプリからの Web API の呼び出しに関するページをご覧ください。

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

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

クライアントは AddAuthenticationStateDeserialization を呼び出して、サーバーによって渡された認証状態を逆シリアル化して使用します。 認証状態は、WebAssembly アプリケーションの有効期間中は変わりません。

PersistentAuthenticationStateProvider クラス (PersistentAuthenticationStateProvider.cs) は、ページがサーバーでレンダリングされたときにページに保持されていたデータを探すことでユーザーの認証状態を決定するクライアント側 AuthenticationStateProvider です。 認証状態は、WebAssembly アプリケーションの有効期間中は変わりません。

ユーザーがログインまたはログアウトする必要がある場合は、ページ全体の再読み込みが必要です。

サンプル アプリでは、表示のためにユーザー名とメールアドレスだけを提供します。 以降の要求を行うときにサーバーに対する認証を行うトークンは含まれません。これは、サーバーへの cookie 要求に含まれる HttpClient を使用して別に処理されます。

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

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

構成

プロジェクトの JwtBearerOptions ファイルに含まれる AddJwtBearer 呼び出しの Program で以下のようにプロジェクトを構成します。

  • Audience: 受け取った OpenID Connect トークンに対して対象ユーザーを設定します。

    Azure portal または Entra ポータル: 以下のように [API の公開]Weather.Get スコープを追加するときに構成されたアプリケーション ID URI のパスに値を一致させます。

    jwtOptions.Audience = "{APP ID URI}";
    

    例:

    アプリ ID URI ({APP ID URI}): https://{DIRECTORY NAME}.onmicrosoft.com/{CLIENT ID}:

    • ディレクトリ名 ({DIRECTORY NAME}): contoso
    • アプリケーション (クライアント) ID ({CLIENT ID}): 00001111-aaaa-2222-bbbb-3333cccc4444
    jwtOptions.Audience = "https://contoso.onmicrosoft.com/00001111-aaaa-2222-bbbb-3333cccc4444";
    

    前の例は、種類が AAD B2C テナントであるテナントに登録されたアプリに関するものです。 アプリが ME-ID テナントに登録されている場合、アプリ ID URI が異なるため、対象ユーザーも異なります。

    例:

    アプリ ID URI ({APP ID URI}): api://{CLIENT ID}、アプリケーション (クライアント) ID ({CLIENT ID}): 00001111-aaaa-2222-bbbb-3333cccc4444

    jwtOptions.Audience = "api://00001111-aaaa-2222-bbbb-3333cccc4444";
    
  • Authority: OpenID Connect の呼び出しを行うためのオーソリティを設定します。 以下のように BlazorWebAppOidc/Program.cs で OIDC ハンドラーに対して構成されているオーソリティに値を一致させます。

    jwtOptions.Authority = "{AUTHORITY}";
    

    例:

    オーソリティ ({AUTHORITY}): https://login.microsoftonline.com/aaaabbbb-0000-cccc-1111-dddd2222eeee/v2.0/ (テナント ID aaaabbbb-0000-cccc-1111-dddd2222eeee を使用)

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

    前の例は、種類が AAD B2C テナントであるテナントに登録されたアプリに関するものです。 アプリが ME-ID テナントに登録されている場合、オソリティは iss プロバイダーから返された JWT の発行者 (identity) と一致する必要があります。

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

気象データ用の Minimal API

以下のようにプロジェクトの 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] 属性をコントローラーまたはアクションに追加します。

サインアウト時に home ページにリダイレクトする

ユーザーがアプリ内を移動すると、LogInOrOut コンポーネント (Layout/LogInOrOut.razor) によって、戻り URL (ReturnUrl) の非表示フィールドが現在の URL (currentURL) の値に設定されます。 ユーザーがアプリからサインアウトすると、identity プロバイダーはサインアウト元のページにユーザーを返します。

ユーザーがセキュリティで保護されたページからサインアウトする場合、サインアウト後に同じセキュリティで保護されたページに戻され、認証プロセスを再び受けることになります。 この動作は、ユーザーが頻繁にアカウントを切り替える必要がある場合には問題ありません。 しかし、アプリの仕様によっては、サインアウト後にユーザーをアプリの home ページまたは他のページに戻すことが必要な場合があります。 次の例は、サインアウト操作の戻り URL としてアプリの home ページを設定する方法を示しています。

次の例では、LogInOrOut コンポーネントに対する重要な変更が示されています。 既定のパスであるため、ReturnUrlのhome ページに設定されている/の非表示フィールドを指定する必要はありません。 IDisposable は実装されなくなりました。 NavigationManager は挿入されなくなりました。 @code ブロック全体が削除されます。

Layout/LogInOrOut.razor:

@using Microsoft.AspNetCore.Authorization

<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 @context.User.Identity?.Name
                </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>

トークンの更新

カスタム cookie リフレッシャー (CookieOidcRefresher.cs) 実装では、有効期限が切れると、ユーザーの要求が自動的に更新されます。 現在の実装では、更新トークンと引き換えにトークン エンドポイントから ID トークンを受け取る必要があります。 この ID トークン内の要求は、ユーザーの要求を上書きするために使用されます。

サンプル実装には、トークンの更新時に UserInfo エンドポイント から要求を要求するためのコードは含まれません。 詳細については、BlazorWebAppOidc AddOpenIdConnect with GetClaimsFromUserInfoEndpoint = true doesn't propogate role claims to client (dotnet/aspnetcore #58826)を参照してください。

Note

一部の identity プロバイダー 、更新トークンを使用する場合にのみアクセス トークンを返します。 CookieOidcRefresher を追加のロジックで更新して、認証 cookie に格納されている要求の以前のセットを引き続き使用するか、アクセス トークンを使用して UserInfo エンドポイントから要求を要求できます。

暗号化 nonce

nonce は、リプレイ攻撃を軽減するためにクライアントのセッションを ID トークンに関連付ける文字列値です。

認証の開発とテスト中に nonce エラーが発生した場合は、古い cookie データによって nonce エラーが発生する可能性があるため、アプリまたはテスト ユーザーに加えられた変更がどれだけ小さくても、テスト実行ごとに新しい InPrivate/incognito ブラウザー セッションを使用します。 詳細については、「Cookie とサイト データ」セクションをご覧ください。

更新トークンが新しいアクセス トークンと交換されるときには、nonce が要求されたり使用されることはありません。 サンプル アプリでは、CookieOidcRefresher (CookieOidcRefresher.cs) が意図的に OpenIdConnectProtocolValidator.RequireNoncefalse に設定しています。

Microsoft Entra (ME-ID) に登録されていないアプリのアプリケーション ロール

このセクションは、 プロバイダーとして identity を使用しないアプリに関連しています。 ME-ID で登録されたアプリについては、「Microsoft Entra (ME-ID) に登録されているアプリのアプリケーション ロール」セクションをご覧ください。

TokenValidationParameters.RoleClaimTypeOpenIdConnectOptions でロール要求の種類 (Program.cs) を構成します。

oidcOptions.TokenValidationParameters.RoleClaimType = "{ROLE CLAIM TYPE}";

多くの OIDC identity プロバイダーの場合、ロール要求の種類は role になります。 identity プロバイダーのドキュメントで正しい値を確認します。

UserInfo プロジェクトの BlazorWebAppOidc.Client クラスを、次のクラスに置き換えます。

UserInfo.cs:

using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using System.Security.Claims;

namespace BlazorWebAppOidc.Client;

// Add properties to this class and update the server and client 
// AuthenticationStateProviders to expose more information about 
// the authenticated user to the client.
public sealed class UserInfo
{
    public required string UserId { get; init; }
    public required string Name { get; init; }
    public required string[] Roles { get; init; }

    public const string UserIdClaimType = "sub";
    public const string NameClaimType = "name";
    private const string RoleClaimType = "role";

    public static UserInfo FromClaimsPrincipal(ClaimsPrincipal principal) =>
        new()
        {
            UserId = GetRequiredClaim(principal, UserIdClaimType),
            Name = GetRequiredClaim(principal, NameClaimType),
            Roles = principal.FindAll(RoleClaimType).Select(c => c.Value)
                .ToArray(),
        };

    public ClaimsPrincipal ToClaimsPrincipal() =>
        new(new ClaimsIdentity(
            Roles.Select(role => new Claim(RoleClaimType, role))
                .Concat([
                    new Claim(UserIdClaimType, UserId),
                    new Claim(NameClaimType, Name),
                ]),
            authenticationType: nameof(UserInfo),
            nameType: NameClaimType,
            roleType: RoleClaimType));

    private static string GetRequiredClaim(ClaimsPrincipal principal,
        string claimType) =>
            principal.FindFirst(claimType)?.Value ??
            throw new InvalidOperationException(
                $"Could not find required '{claimType}' claim.");
}

この時点で、Razor コンポーネントでは、ロール ベースの承認とポリシー ベースの承認を採用できます。 アプリケーション ロールは、role 要求 (ロールごとに 1 つの要求) に表示されます。

Microsoft Entra (ME-ID) に登録されているアプリのアプリケーション ロール

このセクションのガイダンスを使用して、Microsoft Entra ID (ME-ID) を使用するアプリのアプリケーション ロール、ME-ID セキュリティ グループ、ME-ID 組み込み管理者ロールを実装します。

このセクションで説明しているアプローチでは、認証 cookie ヘッダーでグループとロールを送信するように ME-ID を構成します。 ユーザーが少数のセキュリティ グループおよびロールのメンバーのみである場合は、ほとんどのホスティング プラットフォームに対して次のアプローチが効果的で、ヘッダーが長すぎる問題 (たとえば、既定のヘッダー長制限が 16 KB (MaxRequestBytes) の IIS ホスティングの場合など) が発生することはありません。 グループまたはロールのメンバーシップが多いためにヘッダーの長さが問題になる場合は、このセクションのガイダンスに従わずに、Microsoft Graph を実装してユーザーのグループとロールを ME-ID から個別に取得することをお勧めします。このアプローチでは、認証 cookie のサイズが大きくなりません。 詳細については、「要求が正しくありません - 要求が長すぎます - IIS サーバー (dotnet/aspnetcore #57545)」をご覧ください。

TokenValidationParameters.RoleClaimTypeOpenIdConnectOptions でロール要求の種類 (Program.cs) を構成します。 その値を roles に設定します。

oidcOptions.TokenValidationParameters.RoleClaimType = "roles";

ME-ID Premium アカウントがないとグループにロールを割り当てることはできませんが、Standard Azure アカウントを使用して、ユーザーにロールを割り当て、ユーザーに対するロール要求を受け取ることができます。 このセクションのガイダンスでは、ME-ID Premium アカウントは必要ありません。

既定のディレクトリを使用する場合は、「アプリケーションにアプリ ロールを追加してトークンで受け取る (ME-ID ドキュメント) 」のガイダンスに従い、ロールを構成して割り当てます。 既定のディレクトリを使用していない場合は、Azure portal でアプリのマニフェストを編集し、マニフェスト ファイルの appRoles エントリでアプリのロールを手動で確立します。 詳しくは、「ロール要求の構成 (ME-ID ドキュメント)」をご覧ください。

ユーザーの Azure セキュリティ グループが groups 要求に到着し、ユーザーの組み込みの ME-ID 管理者ロールの割り当てが既知の ID (wids) 要求に到着します。 両方の要求の種類の値は GUID です。 アプリで受信されると、これらの要求を使用して、Razor コンポーネントでロールとポリシーの承認を確立できます

Azure portal のアプリ マニフェストで、groupMembershipClaims 属性All に設定します。 All の値を設定すると、ME-ID はサインインしたすべてのセキュリティおよび配布グループ (groups 要求) とロール (wids 要求) を送信します。 groupMembershipClaims 属性を次に設定します。

  1. Azure portal で [アプリの登録] を開きます。
  2. サイド バーで [管理]>[マニフェスト] を選択します。
  3. groupMembershipClaims 属性を見つけます。
  4. 値を All に設定します ("groupMembershipClaims": "All")。
  5. [保存] ボタンを選択します。

UserInfo プロジェクトの BlazorWebAppOidc.Client クラスを、次のクラスに置き換えます。

UserInfo.cs:

using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using System.Security.Claims;

namespace BlazorWebAppOidc.Client;

// Add properties to this class and update the server and client 
// AuthenticationStateProviders to expose more information about 
// the authenticated user to the client.
public sealed class UserInfo
{
    public required string UserId { get; init; }
    public required string Name { get; init; }
    public required string[] Roles { get; init; }
    public required string[] Groups { get; init; }
    public required string[] Wids { get; init; }

    public const string UserIdClaimType = "sub";
    public const string NameClaimType = "name";
    private const string RoleClaimType = "roles";
    private const string GroupsClaimType = "groups";
    private const string WidsClaimType = "wids";

    public static UserInfo FromClaimsPrincipal(ClaimsPrincipal principal) =>
        new()
        {
            UserId = GetRequiredClaim(principal, UserIdClaimType),
            Name = GetRequiredClaim(principal, NameClaimType),
            Roles = principal.FindAll(RoleClaimType).Select(c => c.Value)
                .ToArray(),
            Groups = principal.FindAll(GroupsClaimType).Select(c => c.Value)
                .ToArray(),
            Wids = principal.FindAll(WidsClaimType).Select(c => c.Value)
                .ToArray(),
        };

    public ClaimsPrincipal ToClaimsPrincipal() =>
        new(new ClaimsIdentity(
            Roles.Select(role => new Claim(RoleClaimType, role))
                .Concat(Groups.Select(role => new Claim(GroupsClaimType, role)))
                .Concat(Wids.Select(role => new Claim(WidsClaimType, role)))
                .Concat([
                    new Claim(UserIdClaimType, UserId),
                    new Claim(NameClaimType, Name),
                ]),
            authenticationType: nameof(UserInfo),
            nameType: NameClaimType,
            roleType: RoleClaimType));

    private static string GetRequiredClaim(ClaimsPrincipal principal,
        string claimType) =>
            principal.FindFirst(claimType)?.Value ??
            throw new InvalidOperationException(
                $"Could not find required '{claimType}' claim.");
}

この時点で、Razor コンポーネントでは、ロール ベースとポリシー ベースの承認を採用できます。

  • アプリケーション ロールは、roles 要求 (ロールごとに 1 つの要求) に表示されます。
  • セキュリティ グループは、グループごとに 1 つの要求として groups 要求に表示されます。 セキュリティ グループ GUID は、セキュリティ グループの作成時に Azure portal に表示され、Identity>[Overview]、>[Groups]>、[View] を選択すると一覧表示されます。
  • 組み込みの ME-ID 管理者ロールは、wids 要求 (ロールごとに 1 つの要求) に表示されます。 wids の値を持つ b79fbf4d-3ef9-4689-8143-76b194e85509 要求では、テナントの非ゲスト アカウントに対して ME-ID によって常に送信され、管理者ロールは参照されません。 管理者ロール GUID (ロール テンプレート ID) は、Azure portal で Roles & admins を選択すると表示され、その後に、省略記号 (...)、>Descriptionで表示されるロールの説明が続きます。 ロール テンプレート ID は、「Microsoft Entra 組み込みロール (Entra ドキュメント)」にも記載されています。

トラブルシューティング

ログ機能

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

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

一般的なエラー

  • アプリまたは 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. アプリを再展開する前に、サーバー上の展開フォルダー内のすべてのファイルを削除します。

Note

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

サーバー アプリを実行する

Blazor Web App のテストとトラブルシューティングを行うときは、サーバー プロジェクトからアプリを実行していることを確認してください。

ユーザーを検査する

次の 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;
    }
}

その他のリソース