ASP.NET Identity のユーザー名、パスワードの検証機能を整理する

過去分

その1 - ASP.NET Identity のセキュリティスタンプ機能を使う
その2 - ASP.NET Identity で二要素認証を使う - SMS 編
その3 - ASP.NET Identity で二要素認証を使う - E-mail 編
その4 - ASP.NET Identity を使って Microsoft アカウントで認証する

今回は今までとちょっと趣を変えて、ASP.NET Identity の立派な機能ではありますが、かなり地味なユーザー名/パスワードの検証機能について調べてみます。

ASP.NET Identity のユーザ名/パスワード検証機能

ASP.NET Identity の UserManger クラスにはユーザ名とパスワードの入力値検証を行う Validator を設定できるようになっています。

具体的な実装例は Visual Studio 2013 で、Web アプリケーションプロジェクトテンプレートを使ってプロジェクトを作成すると確認出来ます。

// IdentityConfig.cs

public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context) 
{
    var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
    // ユーザー名の検証ロジックを設定します
    manager.UserValidator = new UserValidator<ApplicationUser>(manager)
    {
        AllowOnlyAlphanumericUserNames = false,
        RequireUniqueEmail = true
    };

    // パスワードの検証ロジックを設定します
    manager.PasswordValidator = new PasswordValidator
    {
        RequiredLength = 6,
        RequireNonLetterOrDigit = true,
        RequireDigit = true,
        RequireLowercase = true,
        RequireUppercase = true,
    };

    // 以下略

上記の、UserManager クラスが持つ UserValidator プロパティと PasswordValidator プロパティにそれぞれ Validator をセットすることで、同クラスを使ったユーザーの作成/更新やパスワードの更新時などに入力値の検証を行えます。

IIdentityValidator インタフェース

UserManager クラスの UserValidator プロパティと PasswordValidator プロパティは、共に IIdentityValidator インタフェースを受け入れます。
より厳密には、前者は IIdentityValidator<TUser>、後者は IIdentityValidator<string> です。

IIdentityValidator インタフェースの定義は以下の通りです。

public interface IIdentityValidator<in T>
{
    Task<IdentityResult> ValidateAsync(T item);
}

シンプルな、というか単一のメソッドしか持たない単純なインタフェースですね。

戻り値だけが少し特殊で IdentityResult という耳慣れないクラスですが、まぁこれは bool の結果と string 型のエラー一覧を持つ、ASP.NET Identity で使われる単純な Result Object です。

これらに関しては特筆すべきことはないと思います。

UserValidator クラス

UserValidator クラスは、その名の通りユーザー名検証を行う IIdentityValidator インタフェースの実装です。UserValidator<TUser, TKey> を継承しており、実態は後者になります。
MSDNこちら)で見れるようですが、情報が少し古いようです。この項では NuGet からインストールできる最新の正式版である V2.1.0 について説明します。

変更可能な設定は以下です。

名前 規定値 説明
AllowOnlyAlphanumericUserNames true ユーザー名に [A-Za-z0-9@_.] のみを許可するかどうかを設定します。
RequireUniqueEmail false Emai アドレスがシステム内でユニークである必要があるかどうかを設定します。

AllowOnlyAlphanumericUserNames プロパティ

このプロパティが検証する対象は、<TUser> すなわち IUser インタフェースの UserName プロパティです。
このプロパティが true の場合ユーザー名が [A-Za-z0-9@_.\] のみで構成されているかのチェックが行われます。許可される文字からいって、メールアドレスを想定しているようですね。(\は何だろう?)

RequireUniqueEmail プロパティ

このプロパティによって検証される値は当然 Email アドレスですが、IUser<TKey> インタフェースには Email アドレスを表す属性はありません(UserName が想定ではあるようですが、、、)。

ではどこから検証すべき Email アドレスを取得しているかというと、UserValidator クラスのコンストラクタで受け取った UserManager クラスのインスタンスが保持する、IUserEmailStore (の GetEmailAsync メソッド)からです。

ちょっと文字なのでわかりにくかったかもしれませんが、簡単に表すと

UserValidator => UserManager => IUserEmailStore.GetEmailAsync

という流れで Email アドレスを取得しようとします。

また、システム内でユニークであるか、つまりシステム内に既に存在する Email アドレスではないか、をチェックしてくれるだけでなく、Email アドレスとして妥当であるかも同時に検証してくれます。

UserValidator を利用する際の注意点

直前で

どこから 検証すべき Email アドレスを取得しているかというと、UserValidator クラスのコンストラクタで受け取った UserManager クラスが保持する IUserEmailStore (の GetEmailAsync メソッド)からです。

と書きましたが、これはそのまま注意点でもあります。

どういうことかと言うと、UserManager が保持する UserStore は、IUserEmailStore を実装している必要がある ということです。

普通に Microsoft.AspNet.Identity.EntityFramework を使っている分には気づくことはないと思いますが、UserStore を自前で実装するような時は注意が必要です。残念ながらこの仕様(だよね?多分)は MSDN には明記されていません。

また、その IUserEmailStore の GetEmailAsync メソッドの実装にも気を遣う必要があります。

UserManager から UserValidator の ValidateAsync メソッドが呼ばれるのは、ユーザーの作成と更新時です。この内、後者は問題ありませんが、前者で問題が出てきます。

なぜなら、(普通は)ユーザーの作成時には、DB に Email アドレスを取得すべきユーザーが存在しないためです。

うかつに引数の TUser インスタンスから DB への select 結果(例えば null や空文字)を返そうとすると、RequireUniqueEmail による Validation は Email アドレスとして正しいかも検証しているので null や空文字が引っ掛かり、ユーザーの作成時に必ず検証失敗が返ってくることになってしまいます。

じゃあどうすればいいの?というと、引数の TUser から取ってくるしかないのですが、TUser が実装している IUser インタフェースは Email アドレスを属性として持たないこのジレンマ。

結局、RequireUniqueEmail プロパティを有効化させるためには、IUser を実装してかつ Email アドレスを属性として持つクラスを作る必要があります。

もし Entity Framework を使っている、もしくは、使わないが Microsoft.AspNet.Identity.EntityFramework を参照しても構わないのであれば、同名前空間に Email アドレス(やそれ以外にも)の属性を持つ IdentityUser クラス(※)を使うことも出来ます。

※情報が古いんですが、、、一応 MSDN

PasswordValidator クラス

PasswordValidator クラスも、その名の通りパスワードの検証を行う IIdentityValidator インタフェースの実装です。

変更可能な設定は以下の通りです。

名前 規定値 説明
RequireDigit 0 パスワードに数字 [0-9] が含まれるべきかを設定します。
RequiredLength false パスワードに最低限必要な文字数を設定します。
RequireLowercase false パスワードにアルファベット小文字 [a-z] が含まれるべきかを設定します。
RequireNonLetterOrDigit false パスワードに記号(=数字/アルファベット[0-9a-zA-Z]以外の文字)が含まれるべきかを設定します。
RequireUppercase false パスワードにアルファベット大文字 [A-Z] が含まれるべきかを設定します。

いずれも単純な規則ですので、特に個別の説明は不要だと思います。

文字数を除く全ての設定を ON にした場合、「数字、大文字、小文字、記号全てを含む文字列」というルールになり、そしてこれが PasswordValidator の最も厳しい規則になります。

PasswordValidator を利用する際の注意点

PasswordValidator による Validation が行われるのは、

  • ユーザーの作成時
  • パスワードの追加/更新時

です。

ユーザーの更新時にはパスワードの Validation は行われません

これはなぜそうなっているのか分からないのですが、実装を見ても Validation は行われていなかったので振る舞いとしては間違いないようです。

パスワードの更新は専用の UI などによる、ユーザー情報の更新とは別の処理フローで行われるべき(パスワードの更新はパスワードの更新のみ独立すべき)だという考えなんですかね。ものがものだけに、そうは言っても出来れば検証しておいて欲しいと思うんですが、、、よく分かりません。

まとめ

UserValidator クラスと PasswordValidator クラスとによって、簡単にユーザー名/パスワードの検証が出来ると言うことが分かりましたが、各々ちょっとした罠もあります。

ASP.NET Identity は、(MSDN 含め)まだ情報があまり充実していないのでたまに苦労しますね。最初のバージョンが出てから一年以上経つので事例は結構ありそうなんですが。。。