前回の「ASP.NET Core Identity の2要素認証と Identity を使わない Cookie 認証 – 概要編」では、ユーザ機能を中心に紹介しました。今回は、そのユーザ機能を実現するために必要な、「ASP.NET Core Identity の構成」について紹介します。
ASP.NET Core Identity
プロジェクト作成
まず最初に、ASP.NET Core MVC プロジェクトを作成します。
ASP.NET Core アプリケーションのプロジェクトを作成
Web アプリケーション(モデル ビュー コントローラー)
[ 認証の変更 ] ボタンをクリックして、「個別のユーザアカウント」に変更します。
プロジェクトの構成
作成したプロジェクトは、次のような構成になっているはずです。
ASP.NET Core Identity データベースを初期化
次にパッケージマネジャーコンソールから Update-Database コマンドを実行して ASP.NET Core Identity データベースを初期化します。
これで、ASP.NET Core Identity を構成するための下準備ができました。
ASP.NET Core Identity を構成
ASP.NET Core Identity をサービス(再利用可能なコンポーネント)として StartUp.cs ConfigureServices メソッド内で登録します。これにより、DI を介して、例えば Controller で利用できます(Controller のコンストラクターへサービスの”挿入”)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// サンプル #region == constructor == // 依存関係の挿入とは、他のオブジェクトが必要とする任意のオブジェクトのことで、ここでは LoginController に必要なオブジェクトをコンストラクタ経由で渡しています。 public LoginController(ILogger<LoginController> logger, IDistributedCache cache, UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager, IOptions<ServiceOption> options) { _userManager = userManager; _signInManager = signInManager; ・・・ } #endregion |
それでは、Startup.cs ConfigureServices(IServiceCollection services) にコードを追加して ASP.NET Core Identity を使えるようにしていきます。
データベース コンテキスト
プロジェクト作成時に Data / ApplicationDbContext.cs が作成されています。また、Startup.cs ConfigureServices にデフォルトで次のコードが用意されています。
1 2 3 4 |
// IdentityDbContext を継承した ApplicationDbContext をサービスに追加します services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer( Configuration.GetConnectionString("DefaultConnection"))); |
ApplicationDbContext クラスは、IdentityDbContext クラス(Identity に必要な Entity Frameworkデータベースコンテキストの基本クラス)を継承しています。
Identity サービス
デフォルトで AddDefaultIdentity が呼ばれ、Identity をサービスに追加しています。
1 2 3 4 5 |
services.AddDefaultIdentity<IdentityUser>() .AddDefaultUI(UIFramework.Bootstrap4) // Identity エラーを日本語化した .AddErrorDescriber<IdentityErrorDescriberJP>() を追加 .AddErrorDescriber<IdentityErrorDescriberJP>() .AddEntityFrameworkStores<ApplicationDbContext>(); |
IdentityErrorDescriberJP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public class IdentityErrorDescriberJP : IdentityErrorDescriber { public override IdentityError DefaultError() => new IdentityError { Code = nameof(DefaultError), Description = $"An unknown failure has occurred." }; public override IdentityError ConcurrencyFailure() => new IdentityError { Code = nameof(ConcurrencyFailure), Description = "Optimistic concurrency failure, object has been modified." }; public override IdentityError PasswordMismatch() => new IdentityError { Code = nameof(PasswordMismatch), Description = "Incorrect password." }; public override IdentityError InvalidToken() => new IdentityError { Code = nameof(InvalidToken), Description = "Invalid token." }; public override IdentityError LoginAlreadyAssociated() => new IdentityError { Code = nameof(LoginAlreadyAssociated), Description = "A user with this login already exists." }; public override IdentityError InvalidUserName(string userName) => new IdentityError { Code = nameof(InvalidUserName), Description = $"User name '{userName}' is invalid, can only contain letters or digits." }; public override IdentityError InvalidEmail(string email) => new IdentityError { Code = nameof(InvalidEmail), Description = $"Email '{email}' is invalid." }; public override IdentityError DuplicateUserName(string userName) => new IdentityError { Code = nameof(DuplicateUserName), Description = $"User Name '{userName}' is already taken." }; public override IdentityError DuplicateEmail(string email) => new IdentityError { Code = nameof(DuplicateEmail), Description = $"Email '{email}' is already taken." }; public override IdentityError InvalidRoleName(string role) => new IdentityError { Code = nameof(InvalidRoleName), Description = $"Role name '{role}' is invalid." }; public override IdentityError DuplicateRoleName(string role) => new IdentityError { Code = nameof(DuplicateRoleName), Description = $"Role name '{role}' is already taken." }; public override IdentityError UserAlreadyHasPassword() => new IdentityError { Code = nameof(UserAlreadyHasPassword), Description = "User already has a password set." }; public override IdentityError UserLockoutNotEnabled() => new IdentityError { Code = nameof(UserLockoutNotEnabled), Description = "Lockout is not enabled for this user." }; public override IdentityError UserAlreadyInRole(string role) => new IdentityError { Code = nameof(UserAlreadyInRole), Description = $"User already in role '{role}'." }; public override IdentityError UserNotInRole(string role) => new IdentityError { Code = nameof(UserNotInRole), Description = $"User is not in role '{role}'." }; public override IdentityError PasswordTooShort(int length) => new IdentityError { Code = nameof(PasswordTooShort), Description = $"パスワードは {length} 文字以上必要です。" }; public override IdentityError PasswordRequiresNonAlphanumeric() => new IdentityError { Code = nameof(PasswordRequiresNonAlphanumeric), Description = "パスワードには英数字以外の文字を少なくとも1つ含める必要があります。" }; public override IdentityError PasswordRequiresDigit() => new IdentityError { Code = nameof(PasswordRequiresDigit), Description = "パスワードには '0'-'9' を少なくとも1つ含める必要があります。" }; public override IdentityError PasswordRequiresLower() => new IdentityError { Code = nameof(PasswordRequiresLower), Description = "パスワードには 'a'-'z' を少なくとも1つ含める必要があります。" }; public override IdentityError PasswordRequiresUpper() => new IdentityError { Code = nameof(PasswordRequiresUpper), Description = "パスワードには 'A'-'Z' を少なくとも1つ含める必要があります。" }; } |
では、AddDefaultIdentity メソッド内を簡単に紹介します。
AddDefaultTokenProviders
パスワードのリセット、電子メールの変更、電話番号の変更操作、および2要素認証トークンの生成に使用されるトークンの生成に使用されるデフォルトのトークンプロバイダを追加しています。
AddDefaultUI
ASP.NET Core 2.1 以降では、Identity は、Razor クラスライブラリとして提供されています。どういうことか、現在の状態でデバックしてみましょう。
[ 登録 ] リンクをクリックして表示されたページのURLを確認すると・・・表示されたパスの cshtml ファイルがプロジェクトに見当たりません。前述の通り Razor クラスライブラリとして提供されているためです。では、この Register をオーバーライドするということで、Identity をスキャフォールディングしてみます。
Identity をスキャフォールディング
ソリューションエクスプローラーのプロジェクトを右クリックしてメニューから「新規スキャフォールディングアイテム」を選択します。次に左のツリーから「ID」を選択します。
次にオーバーライドするファイルを選択しますが、ここでは「Login」「Logout」「Register」を選択して、データコンテキストクラスに「ApplicationDbContext」を指定してみましょう。
Identity UI を完全に制御
Razor クラスライブラリ を使用せずに、Identity UI を完全に制御したい場合は、AddDefaultIdentity の代わりに、次のコードを追加して Controller 及び View を実装します。
1 2 3 4 5 6 |
services.AddIdentity<ApplicationUser, IdentityRole>() // コメントアウト //.AddDefaultUI(UIFramework.Bootstrap4) .AddEntityFrameworkStores<ApplicationDbContext>() .AddErrorDescriber<IdentityErrorDescriberJP>() .AddDefaultTokenProviders(); |
IdentityOptions の構成
ASP.NET Core Identity では、ユーザのパスワードポリシー、ロックアウトなど既定値の設定が使用されます。なので作成したプロジェクトの Startup.cs には IdentityOptions の構成コードがありません。ここでは既定値を上書きする方法を紹介します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
services.Configure<IdentityOptions>(options => { // パスワード:0 ~ 9 までの数値が必要 options.Password.RequireDigit = true; // パスワード:小文字の文字が必要 options.Password.RequireLowercase = true; // パスワード:英数字以外の文字が必要 options.Password.RequireNonAlphanumeric = true; // パスワード:大文字が必要 options.Password.RequireUppercase = true; // パスワード:最小長 options.Password.RequiredLength = 10; // パスワード:個別の文字数 options.Password.RequiredUniqueChars = 1; // ロックアウト:ロックアウト継続時間 options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(60); // ロックアウト:何回認証に失敗したらロックアウトするか options.Lockout.MaxFailedAccessAttempts = 5; // ロックアウト:新規ユーザーのロックアウト可否 options.Lockout.AllowedForNewUsers = true; // ユーザ options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+"; options.User.RequireUniqueEmail = false; }); |
ユーザ情報は、AspNetUsers テーブルに格納されます。このテーブルで、パスワード、ロックアウト、2要素認証の有無などを管理します。次回ユーザ作成・更新・削除を紹介したいと思います。
認証 Cookie の構成
AddDefaultIdentity メソッド内の AddIdentityCookies のソースを見ると説明に ”Adds cookie authentication.”とあるので、ASP.NET Core Identity で使用する Cookie を以降、ここでは、認証 Cookie と呼びます。
ASP.NET Core Identity では 認証 Cookie を使用します。ユーザのパスワードポリシー、ロックアウトと同様に既定値が使用されますが、ここでは上書きする方法を紹介します。
注意事項
AddDefaultIdentity、AddIdentity はメソッド内で認証 Cookie の既定値を構成しているため、その後で認証 Cookie の構成を呼び出す必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
services.ConfigureApplicationCookie(options => { // アクセス拒否時のリダイレクト先 options.AccessDeniedPath = "/Login/AccessDenied"; // Cookie 名 options.Cookie.Name = ".HogeHoge.Cookie"; // HttpOnly 属性 options.Cookie.HttpOnly = true; // Cookieに保存されている認証チケットが作成されてからの有効期間 options.ExpireTimeSpan = TimeSpan.FromMinutes(60); // ログインページのパス options.LoginPath = "/Login/Login"; // ReturnUrlParameter requires options.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter; // 現在開いているウインドウ(Webアプリ)の Cookie 有効期間が過ぎている場合に、ログイン中であれば、新しい有効期限で新しいCookieを再発行する options.SlidingExpiration = true; }); |
因みに、Identity モデルのカスタマイズやユーザーストアをカスタマイズをすることが可能です。
セッション管理の構成
ついでにセッション管理の構成もあわせて。Redis を利用したセッション管理を次の記事で紹介しています。
ASP.NET Core Identity を有効化
ASP.NET Core Identity の構成が完了したので、ASP.NET Core Identity をアプリ内で有効にします。といってもデフォルトで認証ミドルウェアを要求パイプラインに追加するコードが用意されています。
1 2 3 |
// Startup.cs の Configure app.UseAuthentication(); app.UseSession(); |
ミドルウェア コンポーネントを追加するにあたり、公式のドキュメントの ミドルウェアの順序 にもありますが、「順序は、セキュリティ、パフォーマンス、および機能にとって重要」ってことを覚えておくといいですよ。
ASP.NET Core Identity の構成以外に2要素認証用のメールサービスなどが必要となりますが、それは次回ということで。ひとまず ASP.NET Core Identity の構成は完了です。
- ASP.NET Core Identity の2要素認証と Identity を使わない Cookie 認証 – 概要編
- ASP.NET Core Identity 構成編
- ASP.NET Core IdentityとSendGridを使用したメールベースの2要素認証