ASP.NET Core 3.1のIdentityで認証の確認メールをSMTPで行う

#初めに
　[「ASP.NET Core 3.1でPostgreSQLを利用してIdentityで認証を使えるようにする」](https://qiita.com/nosa67/items/7830061352b18c5772c1)を記述している中で、除外したユーザー登録のSMTPでの確認メールを試してみました。とりあえず送信できるところまでで、送信時のエラー等の対処は行っていません。このあたりの対処の問題で、マイクロソフトはSMTPの利用を推奨していない様です。

#環境
　今回の環境は以下の通りです。
- VisualStudio2019 Ver.16.6.4
- ASP.NET Core 3.1
- MailKit 2.8.0
- 認証を組み込んだテンプレートでプロジェクトが作成されていること
- データベースのマイグレーションが行われて利用可能になっていること

##実装
###テンプレートの確認
　テンプレートで認証有のプロジェクトを作成すると、「Startup.cs」の「ConfigureServices()」で基本的な認証が以下のように設定されます。


```C#:Startup.cs
...
public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    ...
}
```

　この時に設定されているオプションの「RequireConfirmedAccount」がアカウントの確認を要求するオプションで、メールを利用してアカウントの確認を行うことを要求しています。これが設定されていると、アカウントの確認ができていない利用者ではログインできません。確認が必要ない場合はこのオプションを消してしまえば良いのですが、確認を実行したい場合はメール送信の設定が必要なので、「IEmailSender」インタフェースを実装したクラスを作成してサービス登録する必要があります。

###SMTPパッケージ
　送信でSMTPを利用するために、SMTPのパッケージを利用します。今回はNuGetで「MailKit」パッケージを追加します。

###メール送信パラメータ
　メール送信用の送信元の情報ですが、「appsettings.json」から取得しようと思います。そのため、DIで利用できるようにメール送信パラメータのクラスを作成します。

```C#:SendMailParams.cs

namespace WebApplication1.MailSender
{
    /// <summary>
    /// メール送信のパラメータ
    /// 「appsettings.json」から受け取るために利用
    /// </summary>
    public class SendMailParams
    {
        public string MailServer { get; set; }
        public int Port { get; set; }
        public string User { get; set; }
        public string Password { get; set; }
        public string SendAddress { get; set; }
    }
}
```

　「Startup.cs」の「ConfigureServices」メソッドで以下のようにすることでDIの対象となります。

```C#:Startup.cs
...
public void ConfigureServices(IServiceCollection services)
{
    ...
    services.Configure<SendMailParams>(Configuration.GetSection("SendMailParams"));
    ...
}
```

実際のパラメータは「appsettings.json」で以下のように設定しています。

```json:appsettings.json
{
  ...

  "SendMailParams": {
    "MailServer": "<メールサーバ>",
    "Port": 587,
    "User": "<ログインユーザーID>",
    "Password": "<ログインパスワード>",
    "SendAddress": "<送信元メールアドレス>"
  }
}
```

###メール送信クラス
　認証で利用するメール送信クラスは以下の通りです。エラーの対処などは行っていません。

```C#:MailSender.cs
using MailKit.Net.Smtp;
using MailKit.Security;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.Extensions.Options;
using MimeKit;
using System.Threading.Tasks;

namespace WebApplication1.MailSender
{
    public class MailSender : IEmailSender
    {
        /// <summary>
        /// メール送信のパラメータ
        /// </summary>
        SendMailParams _sendMailParams;

        /// <summary>
        /// メール送信のパラメータをDIで受け取るコンストラクタ
        /// </summary>
        /// <param name="optionsAccessor"></param>
        public MailSender(IOptions<SendMailParams> optionsAccessor)
        {
            _sendMailParams = optionsAccessor.Value;
        }

        /// <summary>
        /// IEmailSenderのメール送信の実装メソッド
        /// </summary>
        /// <param name="email">送信先メールアドレス</param>
        /// <param name="subject">送信メールタイトル</param>
        /// <param name="message">送信メールメッセージ</param>
        /// <returns>メール送信タスク</returns>
        public Task SendEmailAsync(string email, string subject, string message)
        {
            return Execute(subject, message, email);
        }

        /// <summary>
        /// メール送信の実際の動作タスク
        /// </summary>
        /// <param name="subject">送信メールタイトル</param>
        /// <param name="message">送信メールメッセージ</param>
        /// <param name="email">送信先メールアドレス</param>
        /// <returns></returns>
        public async Task Execute(string subject, string message, string email)
        {
            var emailMessage = new MimeMessage();

            emailMessage.From.Add(new MailboxAddress(_sendMailParams.User, _sendMailParams.SendAddress));

            emailMessage.To.Add(new MailboxAddress(email, email));

            emailMessage.Subject = subject;

            emailMessage.Body = new TextPart("plain") { Text = message };

            using (var client = new SmtpClient())
            {
                await client.ConnectAsync (_sendMailParams.MailServer, _sendMailParams.Port, SecureSocketOptions.Auto);
                await client.AuthenticateAsync(_sendMailParams.User, _sendMailParams.Password);
                await client.SendAsync(emailMessage);
                await client.DisconnectAsync(true);
            }
        }
    }
}

```

　これを利用するために最終的に「Startup.cs」の「ConfigureServices」は以下のようになります。

```C#:Startup.cs
...
public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseNpgsql(
                    Configuration.GetConnectionString("DefaultConnection")));

            // メール送信用のパラメータを「appsettings.json」から抜きしてDIで利用できるようにしている
            services.Configure<SendMailParams>(Configuration.GetSection("SendMailParams"));

            // メール送信用のクラスを認証時に利用するメール送信サービスとして登録。これで認証時に確認メールが送信できるようになる
            services.AddScoped<IEmailSender, MailSender.MailSender>();

            // メールによる確認を有効にした認証を有効にする
            services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
                .AddEntityFrameworkStores<ApplicationDbContext>();
            services.AddRazorPages();
        }
```

###テスト
　これでメール送信の設定ができたので、起動して最初の画面の右上の「Regist」でユーザー登録します。もちろん、送信メールサーバーなどの設定や、ネットワークへの接続はできる状態にしておいてください。ユーザー登録を実施すると、登録したメールアドレス宛てに、以下のような確認メールが送信されます。ここで、リンクをクリックして登録を実施すると確認完了画面が表示され、このメールアドレスでログインできるようになります。

```text:確認メール
Please confirm your account by <a href='https://localhost:44359/Identity/Account/ConfirmEmail?userId=...' >clicking here</a>.
```

![confirmRegist.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482873/dd1a6107-b74e-80ef-555e-ecf0cf627c88.png)

　メール内容が英語なので、実際に使うには認証をスキャフォールディングして該当ソースを探して変更する必要がありそうです。