ASP.NET Core Identity を使用して ASP.NET Core Blazor WebAssembly をセキュリティで保護する

ASP.NET Core Identity を使用してスタンドアロン Blazor WebAssembly アプリをセキュリティで保護するには、この記事のガイダンスに従います。

登録、ログイン、およびログアウトのためのエンドポイント

Razor ページに基づく SPA と Blazor アプリ用の ASP.NET Core Identity によって提供される既定の UI を使用する代わりに、バックエンド API 内の MapIdentityApi を呼び出して、ASP.NET Core Identity を使用してユーザーを登録およびログインするための JS ON API エンドポイントを追加します。 Identity API エンドポイントは、2 要素認証やメール検証などの高度な機能もサポートしています。

クライアントで、/register エンドポイントを呼び出して、メール アドレスとパスワードを使用してユーザーを登録します。

var result = await _httpClient.PostAsJsonAsync(
    "register", new
    {
        email,
        password
    });

クライアントで、useCookies クエリ文字列を true に設定した状態で /login エンドポイントを使用して、cookie 認証を使用してユーザーをログインします。

var result = await _httpClient.PostAsJsonAsync(
    "login?useCookies=true", new
    {
        email,
        password
    });

バックエンド サーバー API は、認証ビルダーで AddIdentityCookies への呼び出しを使用して cookie 認証を確立します。

builder.Services
    .AddAuthentication(IdentityConstants.ApplicationScheme)
    .AddIdentityCookies();

認証トークン

一部のクライアントが cookie をサポートしていないネイティブおよびモバイル シナリオのために、ログイン API はトークンを要求するパラメータを提供します。 後続の要求を認証するために使用できるカスタム トークン (ASP.NET Core Identity プラットフォームに固有のもの) が発行されます。 トークンは、ベアラー トークンとして Authorization ヘッダーで渡す必要があります。 更新トークンも提供されます。 このトークンを使用すると、古いトークンの有効期限が切れたときに、ユーザーに再度ログインを強制することなく、アプリで新しいトークンを要求できます。

このトークンは標準の JS ON Web トークン (JWT) ではありません。 組み込み Identity API は主に単純なシナリオ向けであるため、カスタム トークンの使用は意図的です。 トークン オプションは、完全な機能を備えた ID サービス プロバイダーまたはトークン サーバーとしては意図されておらず、cookie を使えないクライアントのために cookie オプションの代わりとなるものです。

次のガイダンスでは、ログイン API を使用してトークンベースの認証を実装するプロセスを開始します。 実装を完了するには、カスタム コードが必要です。 詳しくは、「Identity を使用して SPA の Web API バックエンドをセキュリティで保護する方法」を参照してください。

認証ビルダーで AddIdentityCookies への呼び出しを使用して cookie 認証を確立するバックエンド サーバー API の代わりに、サーバー API は AddBearerToken 拡張メソッドを使用してベアラー トークン認証を設定します。 ベアラー認証トークンのスキームを IdentityConstants.BearerScheme で指定します。

Backend/Program.cs で、認証サービスと構成を次のように変更します。

builder.Services
    .AddAuthentication()
    .AddBearerToken(IdentityConstants.BearerScheme);

BlazorWasmAuth/Identity/CookieAuthenticationStateProvider.cs で、CookieAuthenticationStateProviderLoginAsync メソッドの useCookies クエリ文字列パラメータを削除します。

- login?useCookies=true
+ login

この時点で、クライアント上の AccessTokenResponse を解析し、アクセスおよび更新トークンを管理するためのカスタム コードを提供する必要があります。 詳しくは、「Identity を使用して SPA の Web API バックエンドをセキュリティで保護する方法」を参照してください。

その他のIdentity のシナリオ:

API によって提供されるその他の Identity シナリオについては、Identityを使用して SPA の Web API バックエンドをセキュリティで保護する方法に関するページを参照してください。

  • 選択したエンドポイントをセキュリティで保護する
  • 認証トークン
  • 2 要素認証 (2FA)
  • 回復用コード
  • ユーザー情報の管理

サンプル アプリ

この記事では、サンプル アプリは、バックエンド Web API を介して ASP.NET Core Blazor WebAssembly にアクセスするスタンドアロン Identity アプリの参照として機能します。 デモには、次の 2 つのアプリが含まれています。

  • Backend: ASP.NET Core Identity のユーザー ID ストアが保持されているバックエンド Web API アプリ。
  • BlazorWasmAuth: ユーザー認証を使用するスタンドアロン Blazor WebAssembly フロントエンド アプリ。

次のリンクを使用して、リポジトリのルートから最新バージョンのフォルダーを介してサンプル アプリにアクセスします。 サンプルは.NET 8 以降を対象としています。 サンプル アプリを実行する手順については、BlazorWebAssemblyStandaloneWithIdentity フォルダーの README ファイルを参照してください。

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

バックエンド Web API アプリのパッケージとコード

バックエンド Web API アプリには、ASP.NET Core Identity のユーザー ID ストアが保持されています。

パッケージ

このアプリでは、次の NuGet パッケージが使用されます。

アプリで使用される EF Core データベース プロバイダーがメモリ内プロバイダーと異なる場合は、アプリ内に Microsoft.EntityFrameworkCore.InMemory に対するパッケージ参照を作成しないでください。

アプリのプロジェクト ファイル (.csproj) で、インバリアント グローバリゼーションが構成されます。

サンプル アプリ コード

アプリの設定によりバックエンドとフロントエンドの URL が構成されます。

  • Backend アプリ (BackendUrl): https://localhost:7211
  • BlazorWasmAuth アプリ (FrontendUrl): https://localhost:7171

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

次のセットアップと構成は、アプリの Program ファイルにあります。

cookie 認証を使用したユーザー ID は、AddAuthentication および AddIdentityCookies を呼び出すことで追加されます。 承認チェックのサービスは、AddAuthorizationBuilder への呼び出しによって追加されます。

デモにのみ推奨。アプリでは、データベース コンテキストの登録 (AddDbContext) に EF Core メモリ内データベース プロバイダーが使用されます。 メモリ内データベース プロバイダーを使用すると、アプリを簡単に再起動し、登録とログインのユーザー フローをテストできます。 各実行は新しいデータベース使って開始されますが、アプリにはテスト ユーザー シードのデモ コードが含まれています。これについてはこの記事で後述します。 データベースが SQLite に変更された場合、EF Core 概要チュートリアルに示されているように、ユーザーはセッション間で保存されますが、データベースは移行を通じて作成する必要があります。 運用コードには、SQL Server などの他のリレーショナル プロバイダーを使用できます。

AddIdentityCoreAddEntityFrameworkStoresAddApiEndpoints の呼び出しを介して、EF Core データベースを使用して Identity エンドポイントを公開するように Identity を構成します。

フロントエンド アプリとバックエンド アプリからの要求を許可するために、クロスオリジン リソース共有 (CORS) ポリシーが確立されます。 CORS ポリシーに対してフォールバック URL が構成されます (アプリの設定で提供されていない場合)。

  • Backend アプリ (BackendUrl): https://localhost:5001
  • BlazorWasmAuth アプリ (FrontendUrl): https://localhost:5002

Web API のドキュメントおよび開発テストに対して、Swagger/OpenAPI 用のサービスとエンドポイントが含まれています。 NSwag の詳細については、「NSwag と ASP.NET Core の概要」を参照してください。

ユーザー ロール要求は、/roles エンドポイントの Minimal API から送信されます。

MapIdentityApi<AppUser>() を呼び出すことで、ルートが Identity エンドポイントに対してマップされます。

ログアウト エンドポイント (/Logout) が、ユーザーをサインアウトさせるためにミドルウェア パイプラインで構成されます。

エンドポイントをセキュリティで保護するには、RequireAuthorization 拡張メソッドをルート定義に追加します。 コントローラーの場合は、[Authorize] 属性をコントローラーまたはアクションに追加します。

DbContext インスタンスの初期化と構成の基本パターンの詳細については、EF Core ドキュメントの「DbContext の有効期間、構成、および初期化」を 参照してください。

フロントエンド スタンドアロン Blazor WebAssembly アプリのパッケージとコード

スタンドアロン Blazor WebAssembly フロントエンド アプリは、プライベート Web ページにアクセスするためのユーザー認証と承認を示します。

パッケージ

このアプリでは、次の NuGet パッケージが使用されます。

サンプル アプリ コード

Models フォルダーには、アプリのモデルが含まれています。

IAccountManagement インターフェイス (Identity/CookieHandler.cs) は、アカウント管理サービスを提供します。

CookieAuthenticationStateProvider クラス (Identity/CookieAuthenticationStateProvider.cs) は、cookie ベースの認証の状態を処理し、IAccountManagement インターフェイスによって記述されるアカウント管理サービスの実装を提供します。 LoginAsync メソッドは、trueuseCookies クエリ文字列値を使用して cookie 認証を明示的に有効にします。 このクラスでは認証されたユーザーのロール要求の作成も管理します。

CookieHandler クラス (Identity/CookieHandler.cs) は、Identity を処理して Identity データ ストアを管理する、バックエンド Web API への各要求と共に cookie 資格情報が確実に送信されるようにします。

wwwroot/appsettings.file は、バックエンドとフロントエンドの URL エンドポイントを提供します。

App コンポーネントは、認証状態をカスケード パラメーターとして公開します。 詳細については、「ASP.NET Core の Blazor 認証と承認」を参照してください。

MainLayout コンポーネントNavMenu コンポーネントは、AuthorizeView コンポーネントを使用して、ユーザーの認証状態に基づいてコンテンツを選択的に表示します。

次のコンポーネントは、一般的なユーザー認証タスクを処理し、IAccountManagement サービスを利用します。

PrivatePage コンポーネント (Components/Pages/PrivatePage.razor) には認証が必要で、ユーザーの要求が表示されます。

サービスと構成は、Program ファイル (Program.cs) で提供されます。

  • cookie ハンドラーはスコープ付きサービスとして登録されます。
  • 承認サービスが登録されます。
  • カスタム認証状態プロバイダーはスコープ付きサービスとして登録されます。
  • アカウント管理インターフェイス (IAccountManagement) が登録されます。
  • ベース ホスト URL は、登録済み HTTP クライアント インスタンス用に構成されます。
  • ベース バックエンド URL は、バックエンド Web API との認証操作に使用される登録済み HTTP クライアント インスタンス用に構成されます。 HTTP クライアントでは、要求ごとに cookie 資格情報が確実に送信されるように cookie ハンドラーが使用されます。

ユーザーの認証状態が変更されたときに AuthenticationStateProvider.NotifyAuthenticationStateChanged を呼び出します。 例については、CookieAuthenticationStateProvider クラス (Identity/CookieAuthenticationStateProvider.cs)LoginAsync および LogoutAsync メソッドを参照してください。

警告

AuthorizeView コンポーネントでは、ユーザーを承認するかどうかに応じて UI コンテンツが選択的に表示されます。 AuthorizeView コンポーネントに配置された Blazor WebAssembly アプリ内のコンテンツはすべて認証なしで検出できるため、認証の成功後、機密性の高いコンテンツはバックエンド サーバー ベースの Web API からを取得する必要があります。 詳細については、次のリソースを参照してください。

テスト ユーザー シードのデモ

SeedData クラス (SeedData.cs) では、開発用のテスト ユーザーを作成する方法を示します。 Leela という名前のテスト ユーザーは、メール アドレス leela@contoso.com を使用してアプリにサインインします。 ユーザーのパスワードは Passw0rd! に設定されます。 Leela には、承認のための AdministratorManager ロールが与えられます。これにより、ユーザーは /private-manager-page のマネージャー ページにアクセスできますが、/private-editor-page のエディター ページにはアクセスできません。

警告

運用環境でのテスト ユーザー コードの実行は許可しないでください。 SeedData.InitializeAsync は、Program ファイルの Development 環境でのみ呼び出されます。

if (builder.Environment.IsDevelopment())
{
    await using var scope = app.Services.CreateAsyncScope();
    await SeedData.InitializeAsync(scope.ServiceProvider);
}

ロール

フレームワークの設計の問題 (dotnet/aspnetcore #50037) により、BlazorWasmAuth アプリのユーザーに対するユーザー要求を作成するためにロール要求は manage/info エンドポイントから送り返されません。 ロール要求は、Backend プロジェクトでユーザーが認証された後に CookieAuthenticationStateProvider クラス (Identity/CookieAuthenticationStateProvider.cs)GetAuthenticationStateAsync メソッドで個別の要求を介して個別に管理されます。

CookieAuthenticationStateProvider では、Backend サーバー API プロジェクトの /roles エンドポイントにロール要求が行われます。 応答は、ReadAsStringAsync() を呼び出すことによって文字列に読み込まれます。 JsonSerializer.Deserialize では、文字列をカスタムの RoleClaim 配列に逆シリアル化します。 最後に、要求がユーザーの要求コレクションに追加されます。

Backend サーバー API の Program ファイルでは、Minimal API によって /roles エンドポイントが管理されます。 RoleClaimType の要求は、匿名型選択され、TypedResults.Json を使用して BlazorWasmAuth プロジェクトに戻るためにシリアル化されます。

ロール エンドポイントには、RequireAuthorization を呼び出すことによって承認が必要です。 セキュリティで保護されたサーバー API エンドポイント用のコントローラーを優先して Minimal API を使用しない場合は、必ず、コントローラーまたはアクションに [Authorize] 属性を設定してください。

クロス ドメイン ホスティング (同一サイト構成)

サンプル アプリは、同じドメインで両方のアプリをホストするように構成されています。 Backend アプリを BlazorWasmAuth アプリと異なるドメインでホストする場合、Backend アプリの Program ファイル内の cookie (ConfigureApplicationCookie) を構成するコードのコメントを解除します。 既定値は次のとおりです。

値を次のように変更します。

- options.Cookie.SameSite = SameSiteMode.Lax;
- options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
+ options.Cookie.SameSite = SameSiteMode.None;
+ options.Cookie.SecurePolicy = CookieSecurePolicy.Always;

同一サイト cookie 設定の詳細については、次のリソースを参照してください。

偽造防止サポート

Backend アプリのログアウト エンドポイント (/logout) のみ、クロスサイト リクエスト フォージェリ (CSRF) の脅威を軽減するために注意が必要です。

ログアウト エンドポイントは、CSRF 攻撃を防ぐために空の本文をチェックします。 本文を要求することで、認証 cookie にアクセスする唯一の方法である JavaScript から要求を行う必要があります。 ログアウト エンドポイントには、フォーム ベースの POST ではアクセスできません。 これにより、悪意のあるサイトがユーザーをログアウトするのを防ぐことができます。

さらに、匿名アクセスを防ぐために、エンドポイントは承認 (RequireAuthorization) によって保護されます。

BlazorWasmAuth クライアント アプリは、要求の本文で空のオブジェクト {} を渡すだけで構いません。

ログアウト エンドポイント以外では、偽造防止軽減策application/x-www-form-urlencodedmultipart/form-data、または text/plain としてエンコードされたフォーム データをサーバーに送信する場合にのみ必要です。 Blazor は、ほとんどの場合、フォームの CSRF 軽減策を管理します。 詳しくは、「ASP.NET Core Blazor の認証と認可」と、「ASP.NET Core Blazor フォームの概要」を参照してください。

application/json エンコードされたコンテンツと CORS が有効になっている他のサーバー API エンドポイント (Web API) への要求では、CSRF 保護は必要ありません。 このため、Backend アプリのデータ処理 (/data-processing) エンドポイントに CSRF 保護は必要ありません。 ロール (/roles) エンドポイントは、状態を変更しない GET エンドポイントであるため、CSRF 保護は必要ありません。

トラブルシューティング

ログ機能

Blazor WebAssembly 認証のデバッグまたはトレース ログを有効にするには、「ASP.NET Core Blazor のログ」をご覧ください。

一般的なエラー

各プロジェクトの構成を確認します。 URL が正しいことを確認します。

  • Backend プロジェクト
    • appsettings.json
      • BackendUrl
      • FrontendUrl
    • Backend.http: Backend_HostAddress
  • BlazorWasmAuth プロジェクト: wwwroot/appsettings.json
    • BackendUrl
    • FrontendUrl

構成が正しい場合:

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

  • ブラウザーの開発者ツールを使用して、BlazorWasmAuth アプリと Backend アプリの間のネットワーク トラフィックを調べます。 多くの場合、要求を行った後、エラー メッセージそのもの、または問題の原因究明の手がかりを含むメッセージが、バックエンド アプリによってクライアントに返されます。 開発者ツールのガイダンスは、次の記事にあります。

  • Google Chrome (Google ドキュメント)

  • Microsoft Edge

  • Mozilla Firefox (Mozilla ドキュメント)

ドキュメント チームは、ドキュメントに関するフィードバックと記事のバグに対応します。 記事の下部にある [ドキュメントの問題を開く] を使用して問題を開きます。 このチームは、製品のサポートを提供できません。 アプリのトラブルシューティングに役立つ、いくつかのパブリック サポート フォーラムが用意されています。 次をお勧めします。

上記のフォーラムは、Microsoft が所有または管理するものではありません。

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

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 パッケージ エクスプローラーを使用してください。

ユーザーの要求を検査する

ユーザーの要求に関する問題をトラブルシューティングするには、次の UserClaims コンポーネントをアプリで直接使用することも、さらにカスタマイズするための基礎として使用することもできます。

UserClaims.razor:

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

<PageTitle>User Claims</PageTitle>

<h1>User Claims</h1>

**Name**: @AuthenticatedUser?.Identity?.Name

<h2>Claims</h2>

@foreach (var claim in AuthenticatedUser?.Claims ?? Array.Empty<Claim>())
{
    <p class="claim">@(claim.Type): @claim.Value</p>
}

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

    public ClaimsPrincipal? AuthenticatedUser { get; set; }

    protected override async Task OnInitializedAsync()
    {
        if (AuthenticationState is not null)
        {
            var state = await AuthenticationState;
            AuthenticatedUser = state.User;
        }
    }
}

その他のリソース