ASP.NET Core 3.1 MVC で ASP.NET Identity をユーザー認証に使用し、Email Confirmation と Password Recovery を実装する際、自分的に気になったことを備忘録として書いておきます。
(.NET Framework 版については特に気を付ける点はなさそうなので割愛します。手抜きですみません。基本的な枠組みが Visual Studio のテンプレートで生成されるコードに含まれており、Microsoft のチュートリアル「ログイン、電子メール確認、パスワード リセットを使用して安全な ASP.NET MVC 5 Web アプリを作成する (C#)」に従って生成されたコードに手を加えれば特に苦労なく実装できると思います。 2021/3/14 追記: SendGrid は id と password による認証をサポートしなくなりました。なので、.NET Framework 版 MVC5 の場合もこの記事に書いた Api Key による認証とする必要がありますので注意してください)
基本的には Microsoft のチュートリアル Account confirmation and password recovery in ASP.NET Core に従って実装しますので、詳しくはそのドキュメントを見てください。そのドキュメントの説明だけではよく分からないと思った点を以下に書きます。
(1) プロジェクトの作成
Visual Studio 2019 のテンプレートを利用して ASP.NET Core 3.1 MVC アプリを作成します。
認証関係のソースコードに手を加えるので、アプリ作成の際に認証は「認証なし」のままとしておき、スキャフォールディング機能を利用して ASP.NET Identity を実装し、ソースコードがプロジェクトに含まれるようにします。
詳しい手順は先の記事「ASP.NET Identity で MySQL 利用 (CORE 版)」のステップ「(1) プロジェクトの作成」とステップ「(2) ASP.NET Core Identity の実装」を見てください。
(2) Email Confirmation 実装前の動き
上のステップ (1) でプロジェクトを作成直後(Email Confirmation を実装する前)のユーザー登録の仕組み説明します。それを理解しておいた方がこの後の説明が分かりやすいと思いますので。仕組みをご存じの方はここはスキップして (3) に進んでください。
デフォルトで options.SignIn.RequireConfirmedAccount が true に設定されており(そのコードは Areas/Identity/IdentityHostingStartup.cs にあります)、ユーザー登録に使用する Register ページも Email Confirmation を使用する前提でコーディングされています。ちなみに false に設定すると登録後直ちにログイン状態になります。
Areas/Identity/Pages/Account フォルダにある Register.cshtml.cs を開いて OnPostAsync メソッドのコードを見てください。登録画面にユーザーが email と password を入力して送信するとそのメソッドでユーザー登録処理が行われます。
ユーザー登録に成功すると RegisterConfirmation ページにリダイレクトされます。(実際はリダイレクト前に _emailSender.SendEmailAsync メソッドが実行されますが、デフォルトではメール送信機能は実装されていないので何も起こりません。何故かエラーも出ません)
RegisterConfirmation ページは下の画像のように表示されます。
上の画像の "Click here to confirm your account" には ConfirmEmail ページへのリンクが張ってあり、クエリ文字列に UserId とトークンが設定されています。
リンクをクリックすると ConfirmEmail ページが GET 要求され Areas/Identity/Pages/Account フォルダにある ConfirmEmail.cshtml.cs の _userManager.ConfirmEmailAsync(user, code) メソッドで Email Confirmation が実行(AspNetUsers テーブルの EmailConfirmed フィールドが True に更新)されます。
Email Confirmation に成功すると下の画像の ConfirmEmail ページが表示されます。
この後、右上の [Login] をクリックして Login ページを表示し、登録した email と password を入力してログインできるようになります。
Email Confirmation を実装してメールを送信できるようにすると、ConfirmEmail ページへのリンク(UserId とトークンのクエリ文字列を含む)が本文に含まれたメールが自動的に送信され、それをクリックすると ConfirmEmail ページが GET 要求されて Email Confirmation が実行されるようになります。
RegisterConfirmation ページへのリダイレクトは同様に起こりますが、それには ConfirmEmail ページへのリンクは含まれず、単に "Please check your email to confirm your account." と表示されただけものになります。
以下のステップ (3) 以降にメールの自動送信を含む Email Confirmation の実装方法を述べます
(3) API Key の入手
チュートリアルに従って、メールの送信には SendGrid を利用します。ユーザー登録して API Key を入手してください。
SendGrid は基本的に有償ですが、開発目的限定で送信する量が少ない場合は Free のサービスでよさそうです。Pricing のページを見てください。
(4) SendGrid のインストール
NuGet を利用してプロジェクトに SendGrid をインストールします。
上の画像ではバージョンが 9.12.6 となっていますが、自分が試したときはそれが最新版だったということで、意図的にそのバージョンを選んだわけではありません。
(5) User Secrets
メールの差出人と SendGrid の API Key をユーザーシークレットに設定します。チュートリアルは dotnet コマンドを使っていますが、Visual Studio のソリューションエクスプローラーから開いて設定できます。
上の画像のように操作すると secrets.json ファイルが開くので、それに以下のようにメールの差出人と SendGrid の API Key を設定して保存します。
appsettings.json に設定しても取得できますが、チュートリアルでは、開発時に、Password その他の機密データを秘密の場所に保存できることを紹介する意味でユーザーシークレットを使っているようです。
ユーザーシークレットは開発時の開発マシンのでの利用に限られます。secrets.json ファイルは開発マシンの開発者のユーザープロファイルフォルダー内にあるので当たり前ですが・・・ 詳しくは Safe storage of app secrets in development in ASP.NET Core を見てください。
チュートリアルには AuthMessageSenderOptions クラスの追加と Startup.ConfigureServices へのコードの追加を行うように書かれています。
まず、プロジェクトルート直下に Services フォルダを追加し、その中にクラスファイル AuthMesageSenderOptions.cs を追加します。コードはチュートリアルのものをコピペします。
// AuthMessageSenderOptions クラスの追加
namespace MySQLIdentity.Services
{
public class AuthMessageSenderOptions
{
public string SendGridUser { get; set; }
public string SendGridKey { get; set; }
}
}
次に、Startup.cs の ConfigureServices メソッド内に以下のコードを追加します。
// Startup,cs の ConfigureServices メソッド内に追加
services.Configure<AuthMessageSenderOptions>(Configuration);
これらは、AuthMessageSenderOption クラスを、必要に応じて初期化してそのオブジェクトを DI によって注入するために必要なもののようです。
その設定により、secrets.json ファイルに設定した差出人と API Key 情報は、EmailSender クラスを初期化する際に DI によって注入された AuthMessageSenderOption オブジェクトから取得できます。
なお、secrets.json ファイルを使わなくても、普通に appsettings.json に差出人と API Key 情報を設定しても同様に DI によって取得できます(secrets.json と appsettings.json の��方から探してくるようです)。運用環境にデプロイする際に appsettings.json に移すということではなさそうですが、実際どうすべきかは調べておらず不明です。
(6) EmailSender クラスの実装
メールを送信するため IEmailSender インターフェイスを継承した EmailSender クラスを実装します。
チュートリアルに従ってクラスファイル Services/EmailSender.cs を追加し、それにチュートリアルのコードをそのままコピペしてください。
念のため、以下にコードを貼っておきます。
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.Extensions.Options;
using SendGrid;
using SendGrid.Helpers.Mail;
using System.Threading.Tasks;
namespace MySQLIdentity.Services
{
public class EmailSender : IEmailSender
{
public EmailSender(IOptions<AuthMessageSenderOptions> optionsAccessor)
{
Options = optionsAccessor.Value;
}
public AuthMessageSenderOptions Options { get; }
public Task SendEmailAsync(string email, string subject, string message)
{
return Execute(Options.SendGridKey, subject, message, email);
}
public Task Execute(string apiKey, string subject, string message, string email)
{
var client = new SendGridClient(apiKey);
var msg = new SendGridMessage()
{
From = new EmailAddress("xxxxx", Options.SendGridUser),
Subject = subject,
PlainTextContent = message,
HtmlContent = message
};
msg.AddTo(new EmailAddress(email));
// Disable click tracking.
// See https://sendgrid.com/docs/User_Guide/Settings/tracking.html
msg.SetClickTracking(false, false);
return client.SendEmailAsync(msg);
}
}
}
上のコード From = new EmailAddress("xxxxx", Options.SendGridUser) の xxxxx をメールの差出人のメールアドレスに変更してください。
EmailSender クラスが初期化される際に DI により AuthMessageSenderOptions オブジェクトが注入され、差出人と API Key 情報が取得できるようにコーディングされています。
EmailSender クラスは Register ページと ForgotPassword ページでメールを送信する際に使用されます。その際、DI により EmailSender クラスを初期化してそのオブジェクトを注入するコーディングになっています。それを可能にするため、Startup.cs の ConfigureServices メソッド内に以下のコードを追加します。
// Startup.cs の ConfigureServices メソッド内に追加
services.AddTransient<IEmailSender, EmailSender>();
上記の Startup.cs への設定を忘れるとメール送信機能は動きませんので注意してください。
(7) Register ページの修正
Areas/Identity/Pages/Account フォルダの Register.cshtml.cs を開いて OnPostAsync メソッドのコードを見てください。
ユーザー登録に成功すると変数 callbackUrl に UserId とトークン情報がクエリ文字列として設定された /Account/ConfirmEmail ページへの URL 文字列が設定されます。
デフォルトでは html の a 要素の href 属性にその URL が設定されたメール本文がユーザーが email として登録したメールアドレスに送信されます。
ユーザーが使っているメーラーが html を表示できる設定になっていれば、その a 要素で "clicking here" と表示されるリンクをクリックすれば ConfirmEmail ページが GET 要求され、クエリ文字列に設定された UserId とトークンで Email Confirmation が行われ、ブラウザが立ち上がって ConfirmEmail ページが表示されます。
ユーザーのメーラーが html の表示を許可していない可能性を考えて、html の a 要素を組み立てて送るのではなく、URL そのものを送信したい場合は以下のようにします。
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
"Please confirm your account by: " +
HtmlEncoder.Default.Encode(callbackUrl).Replace("&", "&"));
その際、その際クエリ文字列内の & は & にしないと ConfirmEmail の OnGetAsync(string userId, string code) の code が null になってしまうので注意してください。
上記の結果、送信されたメールは以下の画像のようになります。(メーラーは Windows Live メールです)
(8) RegisterConfirmation ページの修正
Areas/Identity/Pages/Account フォルダの RegisterConfirmation.cshtml.cs を開いて OnGetAsync メソッドを以下のように修正します。
Email Confirmation を実装したので ConfirmationEmail ページへのリンクを表示する必要はありません。そのリンクを生成するコードをコメントアウトします。
加えて DisplayConfirmAccountLink を falses に設定し、ConfirmationEmail ページへのリンクではなくて単に "Please check your email to confirm your account." と表示されるようにします。
(9) Password Recovery
Password Recovery はユーザーがパスワードを忘れてしまっても復活できる手段を提供するものです。
上のステップ「(6) EmailSender クラスの実装」 まででメールは送信されるようになっていますので、基本的な機能だけなら既存のコードに手を加える必要はありません。
なお、スキャフォールディングで実装されるコードはメール送信機能を実装しないと Password Recovery が使えないコーディングになっていますので注意してください。(メール送信機能なしで Password Recovery を利用するにはコードを書き換える必要があります)
以下に Password Recovery がどのような動きになるかを説明しようと思いましたが、長くなりすぎるので別の記事「Password Recovery (CORE 版)」に書きました。興味があればそちらを見てください。