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 を使用してユーザーを登録およびログインするための JSON 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 はトークンを要求するパラメータを提供します。
警告
ブラウザー ベースのアプリケーションにはトークンの代わりに、Cookie を使用することをお勧めします。ブラウザーが Cookie を処理し、JavaScript にそれらを公開しないためです。 Web アプリでトークン ベースのセキュリティを使用することを選択した場合は、トークンのセキュリティを確保する責任があります。
後続の要求を認証するために使用できるカスタム トークン (ASP.NET Core Identity プラットフォームに固有のもの) が発行されます。 トークンは、ベアラー トークンとして Authorization
ヘッダーで渡す必要があります。 更新トークンも提供されます。 このトークンを使用すると、古いトークンの有効期限が切れたときに、ユーザーに再度ログインを強制することなく、アプリで新しいトークンを要求できます。
このトークンは標準の JSON Web トークン (JWT) ではありません。 組み込み Identity API は主に単純なシナリオ向けであるため、カスタム トークンの使用は意図的です。 トークン オプションは、フル機能の identity サービス プロバイダーまたはトークン サーバーになることを目的としたものではなく、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
で、CookieAuthenticationStateProvider
の LoginAsync
メソッドの 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 のユーザー identity ストアが保持されているバックエンド Web API アプリ。BlazorWasmAuth
: ユーザー認証を使用するスタンドアロン Blazor WebAssembly フロントエンド アプリ。
次のリンクを使用して、リポジトリのルートから最新バージョンのフォルダーを介してサンプル アプリにアクセスします。 サンプルは.NET 8 以降を対象としています。 サンプル アプリを実行する手順については、BlazorWebAssemblyStandaloneWithIdentity
フォルダーの README
ファイルを参照してください。
サンプル コードを表示またはダウンロードします (ダウンロード方法)。
バックエンド Web API アプリのパッケージとコード
バックエンド Web API アプリには、ASP.NET Core Identity のユーザー identity ストアが保持されています。
パッケージ
このアプリでは、次の NuGet パッケージが使用されます。
Microsoft.AspNetCore.Identity.EntityFrameworkCore
Microsoft.EntityFrameworkCore.InMemory
NSwag.AspNetCore
アプリで使用される 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 認証を使用したユーザー identity は、AddAuthentication および AddIdentityCookies を呼び出して追加されます。 承認チェックのサービスは、AddAuthorizationBuilder への呼び出しによって追加されます。
デモにのみ推奨。アプリでは、データベース コンテキストの登録 (AddDbContext) に EF Core メモリ内データベース プロバイダーが使用されます。 メモリ内データベース プロバイダーを使用すると、アプリを簡単に再起動し、登録とログインのユーザー フローをテストできます。 各実行は新しいデータベース使って開始されますが、アプリにはテスト ユーザー シードのデモ コードが含まれています。これについてはこの記事で後述します。 データベースが SQLite に変更された場合、ユーザーはセッション間で保存されますが、データベースは移行を通じて作成する必要があります (詳細については、「EF Core 入門チュートリアル」をご覧ください)。† 運用コードには、SQL Server などの他のリレーショナル プロバイダーを使用できます。
Note
† EF Core 入門チュートリアルでは、Visual Studio の使用時に PowerShell コマンドを使用してデータベースの移行を実行します。 Visual Studio では、これとは別に、接続済みサービス UI を使用する方法があります。
ソリューション エクスプローラーで、[Connected Services] をダブルクリックします。 [サービスの依存関係]、>[SQL Server Express LocalDB] で、省略記号 (...
) に続いて、[移行の追加] を選択して移行を作成するか、[データベースの更新] を選択してデータベースを更新します。
AddIdentityCore、AddEntityFrameworkStores、AddApiEndpoints の呼び出しを介して、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 パッケージが使用されます。
Microsoft.AspNetCore.Components.WebAssembly.Authentication
Microsoft.Extensions.Http
Microsoft.AspNetCore.Components.WebAssembly
Microsoft.AspNetCore.Components.WebAssembly.DevServer
サンプル アプリ コード
Models
フォルダーには、アプリのモデルが含まれています。
FormResult
(Identity/Models/FormResult.cs
): ログインと登録に対する応答。UserBasic
(Identity/Models/UserBasic.cs
): 登録およびログインするための基本的なユーザー情報。UserInfo
(Identity/Models/UserInfo.cs
): 要求を確立するための identity エンドポイントからのユーザー情報。
IAccountManagement
インターフェイス (Identity/CookieHandler.cs
) は、アカウント管理サービスを提供します。
CookieAuthenticationStateProvider
クラス (Identity/CookieAuthenticationStateProvider.cs
) は、cookie ベースの認証の状態を処理し、IAccountManagement
インターフェイスによって記述されるアカウント管理サービスの実装を提供します。 LoginAsync
メソッドは、true
の useCookies
クエリ文字列値を使用して cookie 認証を明示的に有効にします。 このクラスでは認証されたユーザーのロール要求の作成も管理します。
CookieHandler
クラス (Identity/CookieHandler.cs
) は、Identity を処理して Identity データ ストアを管理する、バックエンド Web API への各要求と共に cookie 資格情報が確実に送信されるようにします。
wwwroot/appsettings.file
は、バックエンドとフロントエンドの URL エンドポイントを提供します。
App
コンポーネントは、認証状態をカスケード パラメーターとして公開します。 詳細については、「ASP.NET Core の Blazor 認証と承認」を参照してください。
MainLayout
コンポーネントと NavMenu
コンポーネントは、AuthorizeView
コンポーネントを使用して、ユーザーの認証状態に基づいてコンテンツを選択的に表示します。
次のコンポーネントは、一般的なユーザー認証タスクを処理し、IAccountManagement
サービスを利用します。
Register
コンポーネント (Components/Identity/Register.razor
)Login
コンポーネント (Components/Identity/Login.razor
)Logout
コンポーネント (Components/Identity/Logout.razor
)
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 には、承認のための Administrator
と Manager
ロールが与えられます。これにより、ユーザーは /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) を構成するコードのコメントを解除します。 既定値は次のとおりです。
- 同一サイト モード: SameSiteMode.Lax
- 安全なポリシー: CookieSecurePolicy.SameAsRequest
値を次のように変更します。
- 同一サイト モード: SameSiteMode.None
- 安全なポリシー: CookieSecurePolicy.Always
- 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-urlencoded
、multipart/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 ドキュメント)
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
- Microsoft Edge:
- [引数] フィールドに、ブラウザーを 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
など) です。
- Microsoft Edge:
- [フレンドリ名] フィールドに名前を指定します。 たとえば、
Firefox Auth Testing
のようにします。 - [OK] ボタンを選択します。
- アプリでテストを繰り返すたびにブラウザー プロファイルを選択する必要がないようにするには、 [既定値として設定] ボタンでプロファイルを既定値として設定します。
- アプリ、テスト ユーザー、またはプロバイダー構成が変更されるたびに、ブラウザーが IDE によって閉じられていることを確認します。
アプリのアップグレード
開発マシンで .NET Core SDK をアップグレードしたり、アプリ内のパッケージ バージョンを変更したりした直後に、機能しているアプリが失敗することがあります。 場合によっては、パッケージに統一性がないと、メジャー アップグレード実行時にアプリが破壊されることがあります。 これらの問題のほとんどは、次の手順で解決できます。
- コマンド シェルから
dotnet nuget locals all --clear
を実行して、ローカル システムの NuGet パッケージ キャッシュをクリアします。 - プロジェクトのフォルダー
bin
とobj
を削除します。 - プロジェクトを復元してリビルドします。
- アプリを再展開する前に、サーバー上の展開フォルダー内のすべてのファイルを削除します。
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;
}
}
}
その他のリソース
ASP.NET Core