次の方法で共有


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

Note

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

重要

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

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

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

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

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

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

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

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

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

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

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

認証トークン

一部のクライアントが Cookie をサポートしていないネイティブおよびモバイル シナリオのために、ログイン API はトークンを要求するパラメータを提供します。

警告

ブラウザー ベースのアプリケーションにはトークンの代わりに、Cookie を使用することをお勧めします。ブラウザーが Cookie を処理し、JavaScript にそれらを公開しないためです。 Web アプリでトークン ベースのセキュリティを使用することを選択した場合は、トークンのセキュリティを確保する責任があります。

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

このトークンは標準の JSON Web トークン (JWT) ではありません。 組み込み Identity API は主に単純なシナリオ向けであるため、カスタム トークンの使用は意図的です。 トークン オプションは、フル機能の identity サービス プロバイダーまたはトークン サーバーになることを目的としたものではなく、Cookie を使用できないクライアント向けの cookieオプションの代替として使用されます。

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

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

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

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

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

- login?useCookies=true
+ login

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

その他のIdentity のシナリオ:

Blazor ドキュメント セットの対象となるシナリオ:

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

  • 選択したエンドポイントをセキュリティで保護する
  • ユーザー情報の管理

セキュリティで保護された認証フローを使用して機密データと資格情報を維持する

警告

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

サンプル アプリ

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

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

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

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

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

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

パッケージ

このアプリでは、次の 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 ファイルにあります。

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

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

Note

† EF Core 入門チュートリアルでは、Visual Studio の使用時に PowerShell コマンドを使用してデータベースの移行を実行します。 Visual Studio では、これとは別に、接続済みサービス UI を使用する方法があります。

ソリューション エクスプローラーで、[Connected Services] をダブルクリックします。 [サービスの依存関係]、>[SQL Server Express LocalDB] で、省略記号 (...) に続いて、[移行の追加] を選択して移行を作成するか、[データベースの更新] を選択してデータベースを更新します。

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

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

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

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

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

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

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

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

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

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

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

パッケージ

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

サンプル アプリ コード

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

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

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

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

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 を呼び出します。 例については、CookieAuthenticationStateProviderIdentity/CookieAuthenticationStateProvider.cs および メソッドを参照してください。

警告

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

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

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

警告

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

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

ロール

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

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

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

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

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

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

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

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

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

偽造防止サポート

/logout アプリのログアウト エンドポイント (Backend) のみ、クロスサイト リクエスト フォージェリ (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;
        }
    }
}

その他のリソース