ASP.NET Core Identity の拡張性を探る


初めまして、あるいはお久しぶりです。田中康一 a.k.a. MugeSo と申します。現在は株式会社 bitFlyer システム開発部にてシステム改善のお仕事をさせていただいております。
[bitFlyer Advent Calendar 2022](https://qiita.com/advent-calendar/2022/bitflyer)の23日目です。今日はASP.NET Core Identityのトピックをお送りします。

# ASP.NET Core Identity とは
ASP.NET Core Identity はASP.NET Coreでの認証処理を提供するASP.NET Core標準の認証ライブラリ/フレームワークです。
UIも提供されており、データベースに規程されたテーブル群を作成すれば簡単にパスワード認証・2要素認証・外部認証(Google/Facebookなど)を含む認証機能とアカウント管理機能をアプリケーションに追加できます。
また、RBACも提供しており、ロールに基づくクレデンシャル管理を行うことができる設計になっています。

# 標準的な拡張方法
[オフィシャルドキュメント](https://learn.microsoft.com/ja-jp/aspnet/core/security/authentication/?view=aspnetcore-7.0)では「[ASP.NET Cor プロジェクトで Identity へのカスタム ユーザー データを追加、ダウンロード、削除する](https://learn.microsoft.com/ja-jp/aspnet/core/security/authentication/add-user-data?view=aspnetcore-7.0&tabs=visual-studio)」というページで標準の IdentityUser クラスを拡張する方法を紹介しています。

# ディープな拡張方法
標準的な拡張方法では手が届かない部分に変更を加えたい場合もあります。ASP.NET Core Identity では多くのインターフェースを公開しており、これらを実装し差し替えることで多くの拡張を行うことができるように設計されています。

ここではどのようなインターフェースがあり何が変更・拡張できるのかをご紹介します。
## ユーザーデータ関連
IUserStore とその周辺インターフェースはユーザーデータ永続化に関連するインターフェースになります。
ユーザーデータ永続化の実装クラスはすべてIUserStoreインターフェースを実装し、機能ごとにオプションとして各インターフェースを実装する形になります。
ユーザーデータのバリデーションを行うためのインターフェースとして IUserValidator が存在します。

標準的な拡張方法で紹介した方法では Microsoft.AspNetCore.Identity.EntityFrameworkCore として提供されている実装の利用を前提としているため、ロールを除いたユーザーデータが一つのテーブルに保存されるテーブル設計になっていますが、ここで紹介するインターフェースを独自に実装することによりテーブルを分割することも可能になります。

それぞれのインターフェースは型引数 TUser を受け取るジェネリックインターフェースとして定義されています。
また、ユーザーの取得・検索を除く各メソッドは第一引数として TUser  型のユーザーオブジェクトを受け取るように設計されています。これにより、実装によっては定義された引数以外のユーザーごとに異なるパラメーターをユーザーオブジェクトを介して取得・設定できるようになります。
### IUserStore
ユーザーの作成・更新・削除・取得を提供します。また前述の通り、すべてのユーザーデータ永続化クラスはこのインターフェースを実装する必要があります。
### IUserAuthenticationTokenStore
外部認証サービスのトークンの保存・参照を提供します。
### IUserAuthenticatorKeyStore
[RFC6238](https://www.ietf.org/rfc/rfc6238.txt) TOTP認証用シークレットの保存・参照を提供します。
### IUserTwoFactorRecoveryCodeStore
二段階認証のリカバリーコード設定と使用・カウントを提供します。
### IUserTwoFactorStore
ユーザーの二段階認証が有効かのフラグの保存・参照を提供します。
### IUserPasswordStore
ユーザーのパスワード認証用パスワードハッシュの保存・参照・存在確認を提供します。
### IUserSecurityStamp
パスワード変更時やログイン無効化時に既存セッションの無効化に用いる”セキュリティスタンプ”の保存・参照を提供します。セッションに保存されているスタンプとこのストアに保存されているスタンプが一致した場合にセッションを有効とします。
### IUserRoleStore
ユーザーのロールの設定・削除・取得・判定を提供します。また、ロールからのユーザーリスト取得も提供します。
### IUserLoginStore
外部認証サービス設定の設定・削除・リスト取得・外部認証サービスに関連付けられたユーザーの取得を提供します。
### IUserEmailStore
ユーザーのメールアドレスの設定・取得、ノーマライズされたメールアドレスの設定・取得、ノーマライズされたメールアドレスによるユーザーの検索、メールアドレスの認証フラグの設定・取得を提供します。
### IUserPhoneNumberStore
ユーザーの電話番号の設定取得、電話番号によるユーザーの検索、電話番号の認証フラグの設定・取得を提供します。
### IUserClaimStore
ユーザーのクレームの追加・削除・取得、クレームによるユーザーリストの取得を提供します。
### IUserLockoutStore
ユーザーの認証失敗回数によるロックアウトに関するデータ永続化を提供します。このストアには3つの情報の永続化機能があります。1つ目はロックアウトの有効化フラグの設定・取得、2つ目は認証失敗回数のカウントアップ・リセット、3つ目はロックアウト終了日時の設定・取得です。
### IQueriableUserStore
Linqクエリーに基づくユーザー検索を提供します。
### IUserValidator
ユーザーデータのバリデーションを提供します。
## ロール関連
RBACで利用するロール関連のデータ永続化やバリデーションに関わるインターフェースになります。
IUserStoreと同様にIRoleStoreを必須としてそれ以外のインターフェースをオプションとして実装します。

それぞれのインターフェースは型引数 TRole を受け取るジェネリックインターフェースとして定義されています。
また、ロールの取得・検索を除く各メソッドは第一引数として TRole  型のロールオブジェクトを受け取るように設計されています。これにより、実装によっては定義された引数以外のロールごとに異なるパラメーターをロールオブジェクトを介して取得・設定できるようになります。
### IRoleStore
ロールの作成・更新・削除・取得、ロール名の設定・取得、ノーマライズされたロール名の設定・取得、ロールIDの取得を提供します。ロールの取得については、ロールIDまたはノーマライズされたロール名による取得を提供します。
### IQueriableRoleStore
Linqによるロールの検索を提供します。
### IRoleClaimStore
各ロールのクレームの追加・削除・リストとして取得を提供します。
### IRoleValidator
ロール関連のデータのバリデーションを提供します。
## パスワード関連
パスワード処理に関するインターフェースが2つ用意されています。上記二つの永続化層のインターフェース群とは異なり、こちらはロジック・制約の変更を行うことができます。

それぞれのインターフェースは型引数 TUser を受け取るジェネリックインターフェースとして定義されています。
また、ユーザーの取得・検索を除く各メソッドは第一引数として TUser  型のユーザーオブジェクトを受け取るように設計されています。これにより、実装によっては定義された引数以外のユーザーごとに異なるパラメーターをユーザーオブジェクトを介して取得できるようになります。
### IPasswordHasher
パスワードのハッシュ化とパスワードハッシュに対してパスワードが正しいかの検証を提供します。
### IPasswordValidator
パスワードとして利用できる文字列かの検証を提供します。これにより、ユーザーが設定しようとするパスワードに制約を課すことで弱いパスワードを回避して安全性を向上させられます。
# 拡張の限界
以上のように、ASP.NET Core Idenity は多くのインターフェースを提供しており、高い拡張性を備えています。
しかし、IUserStore が標準でノーマライズされた名前を要求していることなど一部過剰な要求がある部分や、弊社の認証仕様に合致しない部分があったため、弊社での採用は見送りました。
# まとめ
いかがだったでしょうか、Identity に限らず、ASP.NET Core のライブラリ群は十分に考慮された拡張性を持った設計になっているため、標準実装では要件に合わない場合でもインターフェイスを実装することで、要件に合った独自実装をしつつ用意された機能を活用することができるケースも多くあります。
公式のドキュメントに十分な説明がない場合でも公開されているソースコードを読むことで新たな拡張点を見つけることができるかもしれません。

次回はYuya Suganoさんの「Blocknative with Flashbotsでmempoolと戯れる #2」です。お楽しみに。