(23/30)みんなでBlazorを学ぼう:ASP.NET Core Identity(3)

(23/30)みんなでBlazorを学ぼう:ASP.NET Core Identity(3)

先に`UserAuthentication()`と`UserAuthorization()`について説明しましたが、これらの違いは:前者はログインしているユーザーが誰かを検証するために使用され、後者はログインユーザーが何をできるかを決定します。

最終更新 2021/12/23 22:19
StrayaWorker
読了目安 3 分
カテゴリ
Blazor
テーマ
みんなでBlazorシリーズを学ぼう
タグ
.NET C# ASP.NET Core Blazor

先ほど述べた UserAuthentication()UserAuthorization() の違いは次の通りです。前者はログインしている人物を確認するためのものであり、後者はログインしている人物が何を実行できるかを決定します。

例えば、従業員が社内システムにログインする際、アカウント(従業員ID、名前、メールアドレスなど)とパスワードを入力する必要があり、システムは誰がログインしたかを認識します。これが Authentication(認証) です。Authentication を処理する方式には CookieTokenサードパーティ認証(OAuth や API-token)OpenIdSAML などがあります。

従業員がシステムにログインした後、一般従業員には通常、部門をまたいだ権限や管理権限はありません。自分自身または所属部門の情報しか見ることができません。例えば、生産部門の従業員は経理部門の財務情報を見ることができませんが、経理部門がコスト計算のために生産部門の原材料価格を見ることができるのは Authorization(認可) です。

ログインしている人物が何を実行できるかを決定する前に、まずログインしている人物が誰かを知る必要があるため、UserAuthentication()UserAuthorization() の前に配置しなければなりません。

ASP.NET Core IdentityClaim ベースの認証を使用しています。Claim を理解するためには、ClaimClaimsIdentityClaimsPrincipal が何かを説明する必要があります。

Claim とはユーザーに関する情報であり、Claim TypeClaim Value(省略可能)で構成されます。Claim には 名前電話番号役割メールアドレス、さらには 役割 などが含まれます。Authorization ではこの Claim を使ってユーザーに権限があるかどうかを判断します。

new System.Security.Claims.Claim(ClaimTypes.Role, role.Name)

ClaimsIdentity は複数の Claim の集合です。例えば、運転免許証 には 名前生年月日電話番号 が記録されており、運転免許証 自体が ClaimsIdentity です。

ClaimsPrincipal は複数の ClaimsIdentity の集合です。台湾人の場合、身分証、健康保険証、運転免許証などを持っており、身分証、健康保険証、運転免許証はそれぞれ ClaimsIdentity であり、それらを保持する人自身が ClaimsPrincipal です。

HTTP requestHttpContext オブジェクトを生成し、そのオブジェクトには現在の request の情報が格納されています。その中に ClaimsPrincipal 型の User というプロパティがあり、このプロパティは UseAuthentication() によって導入される Authentication Middleware によって生成されます。

ログイン機構は Cookie または JWT で実装できますが、Authentication Middleware はどの方式を使って User プロパティを生成するかをどうやって判断するのでしょうか。その答えは Authentication SchemeAuthentication Handlers にあります。

Authentication Handlers

Authentication Handlers は認証を処理する方法です。ASP.NET Core Identity では AuthenticateAsync() API を呼び出してユーザーがログイン済みかどうかを検証し、検証に失敗した場合は ChallengeAsync() を呼び出してユーザーを ログインページ にリダイレクトし、認可に失敗した場合は ForbidAsync() を使ってユーザーのアクセスを禁止します。もちろん、これらの動作を自分で実装することも可能です。次の例では JWTCookie の認証方式を使用しています。前者を使用する場合は JWT token を検証し、ClaimsPrincipal を生成して HttpContext.User に設定します。後者を使用する場合は現在の requestcookie を確認し、ClaimsPrincipal を生成します。

builder.Services.AddAuthentication()
	.AddJwtBearer()
	.AddCookie();

Authentication Scheme

いずれかの方式で Authentication Handlers を登録することを Authentication Scheme と呼びます。各 Authentication Scheme には一意の名前が付けられ、Authentication Handlers を独自に設定することもできます。以下のコードの結果は上記と同じになります。どちらも既定の Scheme 名を持っているためです。

builder.Services.AddAuthentication()
    .AddJwtBearer("Bearer")
    .AddCookie("Cookies");

Blazor Authentication

Blazor が使用する認証方式は ASP.NET Core と同じですが、Blazor WebAssemblyBlazor Server では異なります。前者の認証は、他のフロントエンドサイトと同様にバイパスされる可能性があります。クライアント側のプログラムはユーザーが改変できるため、データを送信する API 側でも認証が必要です。後者では、組み込みの AuthenticationStateProvider を使用して前述の HttpContext.User を取得できます。筆者は以前、この Service を継承してオーバーライドし、JWT 認証を実装しました。

AuthenticationStateProvider は、昨日説明した <AuthorizeView><CascadingAuthenticationState> が現在の認証状態を取得できる理由です。ただし、既定の認証機構をオーバーライドしないのであれば、Component 内で AuthenticationStateProvider を直接注入することは推奨しません。直接使用する場合の欠点は明らかで、現在の request の認証状態が変更されても、その Component の認証機構を変更したことになるため、その Component には変更が通知されません。

以下の図のように、ApiAuthenticationStateProviderAuthenticationStateProvider を継承し、Task<AuthenticationState> をオーバーライドしています。このプロパティから現在の認証状態を取得できます。MarkUserAsAuthenticated()MarkUserAsLoggedOut() は筆者が独自に作成したメソッドで、ユーザーが認証済みであることやログアウトをマークするためのものです。NotifyAuthenticationStateChanged() はその名の通り、各 Component に現在の認証状態を通知します。これが継承とオーバーライドの例です。

次の図は、Component 内で現在の requestHttpContext.User および Claims を取得する方法を示しています。ただし、ここではサービスを使用して User を取得し、その Claims を取得しているため、冗長な処理となっています。

単に HttpContext.User を取得するだけであれば、次の図のようにすれば十分です。Task<AuthenticationState>[CascadingParameter] として階層的に渡されるからです。

参考文献:

  1. Introduction to Authentication in ASP.NET Core
  2. ASP.NET Core Blazor authentication and authorization

注:本文のコードは .NET 6 + Visual Studio 2022 でリファクタリングしています。原文のリンクとリファクタリング後のコードを比較しながら学習していただければ幸いです。ご一読いただきありがとうございます。原著者をサポートします。

さらに探索

関連読書

その他の記事
同じカテゴリ / 同じタグ 2021/12/25

(29/30)みんなで学ぶBlazor:Blazor単体テスト

システム開発において最も退屈なプロセスは、おそらくバグ修正です。特に、null オブジェクトにアクセスしようとするエラー(`Object reference not set to an instance of an object.`)は、多くの初心者が最初に直面する問題です。退屈なバグ修正から解放されるために、この記事では「単体テスト」を紹介します。

続きを読む
同じカテゴリ / 同じタグ 2021/12/25

(28/30)みんなで学ぶBlazor:ポリシーベースの認可

以前に「ASP.NET Core Identity」は「Claim」ベースの検証を使用すると述べましたが、実は「ASP.NET Core Identity」には異なる種類の認可方法があります。最も簡単な「ログイン認可」「ロール認可」「Claim認可」ですが、これらはすべて同じ方法で実現されています:原則認可(ポリシーベースの認可)です。

続きを読む