オウルです。
前回の【初心者向け】ASP.NET Core Identity – 構成編でIdentityの構成をしたので、今回はSendGridを使用したメールベースの2要素認証を紹介します。
WSL v1 | Ubuntu18.04 LTS |
App | ASP.NET Core 3.1.302 |
DB | SQLServer Express 2017 |
スキャフォールディング
Identity Razorクラスライブラリの規定動作を変更する場合は、スキャフォールディングで生成したコードを変更します。VS Codeを使用する場合は、.NET Core CLIでプロジェクトにパッケージをインストールします。では、パッケージインストールをします。
パッケージインストール
依存関係があるため、適切なバージョンをインストールします。
1 2 3 4 5 6 7 8 9 10 11 12 |
// https://www.nuget.org/packages/Microsoft.VisualStudio.Web.CodeGeneration.Design/ dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design --version 3.1.4 // https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.Design/ dotnet add package Microsoft.EntityFrameworkCore.Design --version 3.1.7 // https://www.nuget.org/packages/Microsoft.AspNetCore.Identity.EntityFrameworkCore dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore --version 3.1.7 // https://www.nuget.org/packages/Microsoft.AspNetCore.Identity.UI dotnet add package Microsoft.AspNetCore.Identity.UI --version 3.1.7 // https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.SqlServer/ dotnet add package Microsoft.EntityFrameworkCore.SqlServer --version 3.1.7 // https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.Tools/ dotnet add package Microsoft.EntityFrameworkCore.Tools --version 3.1.7 |
dotnet aspnet-codegeneratorコマンド
スキャフォールディングでコード(ファイル)を生成します。次のコマンドで、どのコード(ファイル)を生成するか一覧で確認できます。
ファイル一覧
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 27 28 29 30 31 32 33 34 35 36 37 38 |
dotnet aspnet-codegenerator identity -lf ----------------------------------------- Finding the generator 'identity'... Running the generator 'identity'... File List: Account._StatusMessage Account.AccessDenied Account.ConfirmEmail Account.ConfirmEmailChange Account.ExternalLogin Account.ForgotPassword Account.ForgotPasswordConfirmation Account.Lockout Account.Login Account.LoginWith2fa Account.LoginWithRecoveryCode Account.Logout Account.Manage._Layout Account.Manage._ManageNav Account.Manage._StatusMessage Account.Manage.ChangePassword Account.Manage.DeletePersonalData Account.Manage.Disable2fa Account.Manage.DownloadPersonalData Account.Manage.Email Account.Manage.EnableAuthenticator Account.Manage.ExternalLogins Account.Manage.GenerateRecoveryCodes Account.Manage.Index Account.Manage.PersonalData Account.Manage.ResetAuthenticator Account.Manage.SetPassword Account.Manage.ShowRecoveryCodes Account.Manage.TwoFactorAuthentication Account.Register Account.RegisterConfirmation Account.ResetPassword Account.ResetPasswordConfirmation |
動作を変更したいファイルを追加
1 2 3 4 |
// -dc: 使用するDbContextクラスを指定 // --files: 追加したいファイル名 // 例えば、Register、Login。LoginWith2faを変更したい場合 dotnet aspnet-codegenerator identity -dc [ApplicationDbContext] --files "Account.Register;Account.Login;Account.LoginWith2fa" |
認証までの流れ
ユーザは、次のオペレーションを行い2要素認証をすることとします。
- ユーザ作成
- 本人確認メール送信
- 本人確認(メールアドレス確認)
- パスワード認証
- 2要素認証
ユーザ作成
ここからは、スキャフォールディングで生成したコード(ファイル)を参考に重要だと思われる箇所を抜粋していきます。
最初にユーザを作成します。コード(ファイル)は”Register.cshtml”と”Register.cshtml.cs”です。
Register.cshtml.cs
次のコードは、ユーザ作成フォームをPOSTした時の処理です。
1 2 3 4 5 6 7 8 |
// 入力されたメールアドレスをUserNameとEmailに代入 var user = new IdentityUser { UserName = Input.Email, Email = Input.Email }; // DIされたUserManager<IdentityUser>を使用してユーザを作成 var result = await _userManager.CreateAsync(user, Input.Password); // ** 2要素認証を無効にしたい場合 ** // ユーザ単位で2要素認証の有効|無効が選択可能 var user = new IdentityUser { UserName = Input.Email, Email = Input.Email, TwoFactorEnabled = false }; |
UserManager
突如、登場したUserManagerですが、AddDefaultIdentityで構成している場合は、AddDefaultIdentityのservices.AddIdentityCoreでサービス登録されています。
また、AddIdentityで構成している場合は、AddIdentityでサービス登録されています。
トークンを生成
ユーザ作成後に入力したメールアドレスにメール送信します。次のコードは、そのメールの本文にあるリンクに埋め込むセキュリティで保護された確認トークンを生成しています。
1 2 |
// トークンには有効期限が含まれている(既定は1日) var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// 規定は1日 // github -> https://github.com/dotnet/aspnetcore/blob/master/src/Identity/Core/src/DataProtectionTokenProviderOptions.cs public class DataProtectionTokenProviderOptions { /// <summary> /// Gets or sets the name of the <see cref="DataProtectorTokenProvider{TUser}"/>. Defaults to DataProtectorTokenProvider. /// </summary> /// <value> /// The name of the <see cref="DataProtectorTokenProvider{TUser}"/>. /// </value> public string Name { get; set; } = "DataProtectorTokenProvider"; /// <summary> /// Gets or sets the amount of time a generated token remains valid. Defaults to 1 day. /// </summary> /// <value> /// The amount of time a generated token remains valid. /// </value> public TimeSpan TokenLifespan { get; set; } = TimeSpan.FromDays(1); } |
トークンの有効期限は既定で1日ですが、すべてのデータ保護トークンのlifespansを変更することもできます。
1 2 3 |
// identityOption.TokenLifespan は appsettings.json をバインドした自作のクラス services.Configure<DataProtectionTokenProviderOptions>(o => o.TokenLifespan = TimeSpan.FromHours(int.Parse(identityOption.TokenLifespan))); |
また、電子メールトークンの有効期間を変更するなど、個別の変更も可能です。
本人確認メール送信
メール送信には、SendGridを使用します。SendGridを使用したメール送信の実装は、SendGrid C#ライブラリを参照ください。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// SendGrid APIを使用してメールを送信 await _emailSender.SendEmailAsync(Input.Email, "Confirm your email", $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>."); // 電子メールの確認を設定する if (_userManager.Options.SignIn.RequireConfirmedAccount) { return RedirectToPage("RegisterConfirmation", new { email = Input.Email }); } else { await _signInManager.SignInAsync(user, isPersistent: false); return LocalRedirect(returnUrl); } |
SendGridでメール送信
SendGridはメール配信サービスです。AzureからSendGridリソースとして作成することもできます。SendGrid を使用した Azure での電子メールの送信方法にもあるように、Azureユーザーは、1 か月あたり 25,000 通の電子メールを無料で利用できます。但し、Pro 以下のプランは共有IPを使用することになるので、ご注意ください。ある日突然、Spamhausのブラックリストに登録されてメールが受信できないなんてこともあり得ます。
本人確認(メールアドレス確認)
次のコードは、受信したメールのリンクをクリックした時に実行されます。
1 2 3 4 |
// 本人確認メール送信時に生成したトークンをエンコード code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code)); // トークンを検証 var result = await _userManager.ConfirmEmailAsync(user, code); |
トークンの検証に問題がなければ、[データベース].[AspNetUsers].[EmailConfirmed]カラムの値が1(bit)になり、メールアドレス確認済(本人確認済)ステータスとなります。
ログイン
ユーザ作成、メールアドレス確認が完了しましたので、次はパスワード認証(ログイン)です。パスワード認証のコード(ファイル)は”Login.cshtml”と”Login.cshtml.cs”になります。
パスワード認証
次のコードは、ログインフォームをPOSTした時の処理です。
Login.cshtml.cs
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 27 28 29 30 31 |
// DIされたSignInManager<IdentityUser>を使用してパスワード認証 var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: true); // パスワード認証が成功、且つ2要素認証が有効化されていない場合 if (result.Succeeded) { _logger.LogInformation("User logged in."); return LocalRedirect(returnUrl); } // パスワード認証が成功、且つ2要素認証が有効化されている場合 if (result.RequiresTwoFactor) { return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe }); } // ロックアウトされている場合 if (result.IsLockedOut) { _logger.LogWarning("User account locked out."); return RedirectToPage("./Lockout"); } // 認証エラー: パスワード、若しくはアカウントに誤りがある else { ModelState.AddModelError(string.Empty, "Invalid login attempt."); return Page(); } |
SignInManager
SignInManagerはAddDefaultUIでサービス登録されています。
2要素認証
いよいよ2要素認証です。コード(ファイル)は”LoginWith2fa.cshtml”と”LoginWith2fa.cshtml.cs”です。
LoginWith2fa.cshtml.cs
1 2 3 4 5 6 7 8 9 10 11 |
// ユーザを取得 var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); // 2要素認証で使用するセキュリティコードを生成 var code = await _userManager.GenerateTwoFactorTokenAsync(user, _2faProvider); if (string.IsNullOrWhiteSpace(code)) { _logger.LogError("is null or whitespace : GenerateTwoFactorTokenAsync"); return LocalRedirect(returnUrl); } // セキュリティコードをメールで送信 await _emailSender.SendMailAsync(await _userManager.GetEmailAsync(user), "Two Factor Authentication Code", $"security code: {code}", $"security code: {code}"); |
2要素認証
1 2 3 |
private readonly string _2faProvider = "Email"; // セキュリティコードを検証 var result = await _signInManager.TwoFactorSignInAsync(_2faProvider, Input.TwoFactorCode, isPersistent: false, rememberClient: false); |
以上が、Identity ASP.NET CoreとSendGridを使用したメールベースの2要素認証の基本的なところになります。ASP.NET Core Identityのフレームワークは機能も豊富、且つカスタマイズも可能です。もちろん、 Facebook、Google、Microsoft アカウント、Twitterなどの外部ログインプロバイダーもサポートされています。かなりボリューム満点で、尻込み気味になりますが、これからも色々試していきたいものです。
また、APIではJWTトークン認証という選択もあります。興味がある方は、こちらも参考にしていただければと思います。
- ASP.NET Core Identity の2要素認証と Identity を使わない Cookie 認証 – 概要編
- ASP.NET Core Identity 構成編
- ASP.NET Core IdentityとSendGridを使用したメールベースの2要素認証