ASP.NET Core でのアカウントの確認とパスワードの回復

作成者: Rick AndersonPonantJoe Audette

このチュートリアルでは、メールの確認とパスワードのリセットを備えた ASP.NET Core アプリを構築する方法について説明します。 このチュートリアルは、最初の記事 ではありません 。 次のことを理解している必要があります。

この記事のガイダンスを追加または置き換える Blazor ガイダンスについては、次のリソースを参照してください。

前提条件

認証を備えた Web アプリを作成してテストする

次のコマンドを実行して、認証を使用して Web アプリを作成します。

dotnet new webapp -au Individual -o WebPWrecover
cd WebPWrecover
dotnet run

シミュレートされた電子メール確認を使用してユーザーを登録する

アプリを実行し、[登録] リンクを選んで、ユーザーを登録します。

登録が完了すると、電子メールの確認をシミュレートするためのリンクが含まれる /Identity/Account/RegisterConfirmation ページにリダイレクトされます。

  1. Click here to confirm your account リンクを選択します。

  2. [ログイン] リンクを選び、同じ資格情報でサインインします。

  3. Hello YourEmail@provider.com! リンクを選ぶと、/Identity/Account/Manage/PersonalData ページにリダイレクトされます。

  4. [ 個人データ ] タブを選択し、[ 削除] を選択します。

Click here to confirm your account インターフェイスがまだ実装されておらず、依存関係挿入コンテナーに登録されていないため、 リンクが表示されます。 詳細については、 RegisterConfirmation ソースを参照してください。

通常、.NET 参照ソースへのドキュメント リンクを使用すると、リポジトリの既定のブランチが読み込まれます。このブランチは、.NET の次回リリースに向けて行われている現在の開発を表します。 特定のリリースのタグを選択するには、[Switch branches or tags](ブランチまたはタグの切り替え) ドロップダウン リストを使います。 詳細については、「ASP.NET Core ソース コードのバージョン タグを選択する方法」 (dotnet/AspNetCore.Docs #26205) を参照してください。

メール プロバイダーを構成する

このチュートリアルでは、Twilio SendGrid を使用して電子メールを送信します。 電子メールを送信するには、SendGrid アカウントとキーが必要です。 メール送信には、SMTP ではなく、SendGrid や他のメール サービスを使うことをお勧めします。 SMTP を正しくセキュリティ保護して設定するのは簡単ではありません。

SendGrid アカウントでは 、送信者の追加が必要になる場合があります。

セキュリティ保護されたメール キーをフェッチするためのクラスを作成します。 このサンプルでは、 Services/AuthMessageSenderOptions.cs ファイルを作成します。

namespace WebPWrecover.Services;

public class AuthMessageSenderOptions
{
    public string? SendGridKey { get; set; }
}

SendGrid のユーザー シークレットを構成する

SendGridKeyを使用して値を設定します。 次に例を示します。

dotnet user-secrets set SendGridKey <key>

Successfully saved SendGridKey to the secret store.

Windowsでは、シークレット マネージャーはキーと値のペアを ディレクトリの %APPDATA%/Microsoft/UserSecrets/<WebAppName-userSecretsId> ファイルに格納します。

secrets.json ファイルの内容は暗号化されません。 次のマークアップは 、secrets.json ファイルを示しています。 SendGridKey値は、この例から削除されます。

{
  "SendGridKey": "<key removed>"
}

詳細については、ASP.NET Core の Options パターン および Configuration を参照してください。

SendGrid をインストールする

このチュートリアルでは、SendGrid を使ってメール通知を追加する方法について説明しますが、他のメール プロバイダーを使うこともできます。

SendGrid NuGet パッケージをインストールします。

パッケージ マネージャー コンソールから、次のコマンドを入力します。

Install-Package SendGrid

無料の SendGrid アカウントに登録するには、 無料の SendGrid Email API 試用版で送信を開始します。

IEmailSender を実装する

IEmailSender インターフェイスを実装するには、次の例のようなコードを含む Services/EmailSender.cs ファイルを作成します。

using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.Extensions.Options;
using SendGrid;
using SendGrid.Helpers.Mail;

namespace WebPWrecover.Services;

public class EmailSender : IEmailSender
{
    private readonly ILogger _logger;

    public EmailSender(IOptions<AuthMessageSenderOptions> optionsAccessor,
                       ILogger<EmailSender> logger)
    {
        Options = optionsAccessor.Value;
        _logger = logger;
    }

    public AuthMessageSenderOptions Options { get; } //Set with Secret Manager.

    public async Task SendEmailAsync(string toEmail, string subject, string message)
    {
        if (string.IsNullOrEmpty(Options.SendGridKey))
        {
            throw new Exception("Null SendGridKey");
        }
        await Execute(Options.SendGridKey, subject, message, toEmail);
    }

    public async Task Execute(string apiKey, string subject, string message, string toEmail)
    {
        var client = new SendGridClient(apiKey);
        var msg = new SendGridMessage()
        {
            From = new EmailAddress("Joe@contoso.com", "Password Recovery"),
            Subject = subject,
            PlainTextContent = message,
            HtmlContent = message
        };
        msg.AddTo(new EmailAddress(toEmail));

        // Disable click tracking.
        // See https://sendgrid.com/docs/User_Guide/Settings/tracking.html
        msg.SetClickTracking(false, false);
        var response = await client.SendEmailAsync(msg);
        _logger.LogInformation(response.IsSuccessStatusCode 
                               ? $"Email to {toEmail} queued successfully!"
                               : $"Failure Email to {toEmail}");
    }
}

電子メールをサポートするようにアプリを構成する

次のタスクを実行する Program.cs ファイルに次のコードを追加します。

  • EmailSender インスタンスを一時的なサービスとして追加します。
  • AuthMessageSenderOptions構成インスタンスを登録します。
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.EntityFrameworkCore;
using WebPWrecover.Data;
using WebPWrecover.Services;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlite(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();

builder.Services.AddTransient<IEmailSender, EmailSender>();
builder.Services.Configure<AuthMessageSenderOptions>(builder.Configuration);

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.MapRazorPages();

app.Run();

Account.RegisterConfirmation がスキャフォールディングされている場合に既定のアカウント検証を無効にする

Account.RegisterConfirmation がスキャフォールディングされている場合は、このセクションの手順をすべて実行してください。

Important

Account.RegisterConfirmation がスキャフォールディングされていない場合は、次の手順をスキップして、次のセクションに進んでください。

ユーザーは /Identity/Account/RegisterConfirmation ページにリダイレクトされ、アカウントを確認するためのリンクを選択できます。 既定の Account.RegisterConfirmation は、テスト にのみ 使用されます。 運用アプリでは、自動アカウント検証を無効にする必要があります。

確認済みのアカウントを要求し、登録時にすぐにサインインできないようにするには、スキャフォールディングされた Identity ファイルでを設定します。

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable

using System;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;

namespace WebPWrecover.Areas.Identity.Pages.Account
{
    [AllowAnonymous]
    public class RegisterConfirmationModel : PageModel
    {
        private readonly UserManager<IdentityUser> _userManager;
        private readonly IEmailSender _sender;

        public RegisterConfirmationModel(UserManager<IdentityUser> userManager, IEmailSender sender)
        {
            _userManager = userManager;
            _sender = sender;
        }

        /// <summary>
        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
        ///     directly from your code. This API may change or be removed in future releases.
        /// </summary>
        public string Email { get; set; }

        /// <summary>
        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
        ///     directly from your code. This API may change or be removed in future releases.
        /// </summary>
        public bool DisplayConfirmAccountLink { get; set; }

        /// <summary>
        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
        ///     directly from your code. This API may change or be removed in future releases.
        /// </summary>
        public string EmailConfirmationUrl { get; set; }

        public async Task<IActionResult> OnGetAsync(string email, string returnUrl = null)
        {
            if (email == null)
            {
                return RedirectToPage("/Index");
            }
            returnUrl = returnUrl ?? Url.Content("~/");

            var user = await _userManager.FindByEmailAsync(email);
            if (user == null)
            {
                return NotFound($"Unable to load user with email '{email}'.");
            }

            Email = email;
            // Once you add a real email sender, you should remove this code that lets you confirm the account
            DisplayConfirmAccountLink = false;
            if (DisplayConfirmAccountLink)
            {
                var userId = await _userManager.GetUserIdAsync(user);
                var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
                EmailConfirmationUrl = Url.Page(
                    "/Account/ConfirmEmail",
                    pageHandler: null,
                    values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl },
                    protocol: Request.Scheme);
            }

            return Page();
        }
    }
}

この手順は、 Account.RegisterConfirmation がスキャフォールディングされている場合にのみ必要です。

スキャフォールディングされていない RegisterConfirmation は、 IEmailSender が実装され、 依存関係挿入コンテナーに登録されたときに自動的に検出します。

登録し、メールを確認し、パスワードをリセットする

Web アプリを実行し、アカウント確認とパスワード回復のフローをテストします。

  1. アプリを実行し、新しいユーザーを登録します。

  2. アカウント確認用のリンクが記載されたメールをご確認ください。 メールが届かない場合は、トラブルシューティングの 「メールのデバッグ 」セクションを参照してください。

  3. リンクを選択し、メールを確認します。

  4. メールとパスワードを使ってサインインします。

  5. サインアウトします。

パスワードのリセットをテストする

  1. サインインしている場合は、[ログアウト] を選びます。

  2. [ ログイン ] リンクを選択し、[パスワードを 忘れた 場合] リンクを選択します。

  3. アカウントの登録に使ったメール アドレスを入力します。 アプリは、パスワードをリセットするためのリンクを含む電子メールを送信します。

  4. 送信された電子メール メッセージに移動します。

  5. リンクを選択し、パスワードをリセットします。

パスワードが正常にリセットされたら、メールと新しいパスワードでサインインできます。

確認メールを再送信

このセクションでは、電子メール確認プロセスと関連タスクをサポートするコードについて説明します。

  • まず、[ログイン] ページで [電子メールの再送信の確認] リンクを選択します。

メールとアクティビティのタイムアウトを変更する

非アクティブ状態の既定のタイムアウトは 14 日です。 次のコードでは、非アクティブタイムアウトを 5 日に設定します。

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.EntityFrameworkCore;
using WebPWrecover.Data;
using WebPWrecover.Services;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlite(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();

builder.Services.AddTransient<IEmailSender, EmailSender>();
builder.Services.Configure<AuthMessageSenderOptions>(builder.Configuration);

builder.Services.ConfigureApplicationCookie(o => {
    o.ExpireTimeSpan = TimeSpan.FromDays(5);
    o.SlidingExpiration = true;
});

var app = builder.Build();

// Code removed for brevity

すべてのデータ保護トークンの有効期限を変更する

次のコードでは、すべてのデータ保護トークンのタイムアウト期間を 3 時間に変更します。

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.EntityFrameworkCore;
using WebPWrecover.Data;
using WebPWrecover.Services;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlite(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();

builder.Services.AddTransient<IEmailSender, EmailSender>();
builder.Services.Configure<AuthMessageSenderOptions>(builder.Configuration);

builder.Services.Configure<DataProtectionTokenProviderOptions>(o =>
       o.TokenLifespan = TimeSpan.FromHours(3));

var app = builder.Build();

// Code removed for brevity.

組み込みの Identity ユーザー トークン ( AspNetCore/src/Identity/Extensions.Core/src/TokenOptions.cs ソースを参照) には 、1 日のタイムアウトがあります。

メール トークンの有効期間を変更する

Identity ユーザー トークンの既定のトークン有効期間は 1 日です。

次のコードは、電子メール トークンの有効期間を変更する方法を示しています。

カスタム DataProtectorTokenProvider<TUser> クラスと DataProtectionTokenProviderOptions クラスを追加します。

public class CustomEmailConfirmationTokenProvider<TUser>
                              :  DataProtectorTokenProvider<TUser> where TUser : class
{
    public CustomEmailConfirmationTokenProvider(
        IDataProtectionProvider dataProtectionProvider,
        IOptions<EmailConfirmationTokenProviderOptions> options,
        ILogger<DataProtectorTokenProvider<TUser>> logger)
                                       : base(dataProtectionProvider, options, logger)
    {

    }
}
public class EmailConfirmationTokenProviderOptions : DataProtectionTokenProviderOptions
{
    public EmailConfirmationTokenProviderOptions()
    {
        Name = "EmailDataProtectorTokenProvider";
        TokenLifespan = TimeSpan.FromHours(4);
    }
}

カスタム プロバイダーをサービス コンテナーに追加します。

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.EntityFrameworkCore;
using WebPWrecover.Data;
using WebPWrecover.Services;
using WebPWrecover.TokenProviders;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlite(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(config =>
{
    config.SignIn.RequireConfirmedEmail = true;
    config.Tokens.ProviderMap.Add("CustomEmailConfirmation",
        new TokenProviderDescriptor(
            typeof(CustomEmailConfirmationTokenProvider<IdentityUser>)));
    config.Tokens.EmailConfirmationTokenProvider = "CustomEmailConfirmation";
}).AddEntityFrameworkStores<ApplicationDbContext>();

builder.Services.AddTransient<CustomEmailConfirmationTokenProvider<IdentityUser>>();

builder.Services.AddRazorPages();

builder.Services.AddTransient<IEmailSender, EmailSender>();
builder.Services.Configure<AuthMessageSenderOptions>(builder.Configuration);

var app = builder.Build();

// Code removed for brevity.

メールをデバッグする

電子メール プロセスが期待どおりに動作しない場合は、次のトラブルシューティング手順を試してください。

  • EmailSender.Execute メソッドにブレークポイントを設定し、SendGridClient.SendEmailAsync メソッドが呼び出されていることを確認します。

  • と同様のコードを使用してEmailSender.Executeを作成します。

  • メール アクティビティ ページを確認します。

  • 迷惑メール フォルダーを確認します。

  • Microsoft、Yahoo、Gmail など、別のメール プロバイダーで別のメール エイリアスを試してください。

  • 別のメール アカウントに送信してみます。

ヒント

セキュリティのベスト プラクティスは、テストと開発では運用用のシークレットを使用しないことです。 アプリを Azure に発行する場合は、Azure Web アプリ ポータルで SendGrid シークレットをアプリケーション設定として設定します。 構成システムは、環境変数からキーを読み取るように設定されています。

ソーシャルとローカルのログイン アカウントを結合する

このセクションを完了するには、最初に外部認証プロバイダーを有効にする必要があります。 詳細については、「 ASP.NET Core での Identity での外部ログイン プロバイダーの使用」を参照してください。

このシーケンスでは、 RickAndMSFT@gmail.com メール アドレスが最初にローカル ログインとして作成されます。 ただし、最初にソーシャル ログインとしてアカウントを作成してから、ローカル ログインを追加することができます。

  1. ローカル アカウントとソーシャル アカウントを組み合わせるには、メール アドレス リンクを選択します。

    Web アプリで認証されたユーザーのメール アドレス リンクを選択する方法を示すスクリーンショット。

  2. [ アカウントの管理 ] ページで、[ 管理 ] リンクを選択します。

    認証されたアカウントに関連付けられている外部 (ソーシャル ログイン) が現在ゼロ (0) であることに注意してください。

    認証されたアカウントに関連付けられている 0 個の外部 (ソーシャル ログイン) を示すスクリーンショット。[管理] リンクが強調表示されています。

  3. [ 外部ログインの管理 ] ページで、別のログイン サービスへのリンクを選択します。 サービス プロンプトに従い、アプリの要求を受け入れます。

    次の図では、Facebook が外部認証プロバイダーとして追加されています。

    認証されたアカウントの外部 (ソーシャル) ログインとして追加された Facebook を示すスクリーンショット。

ユーザーの電子メール アドレスの認証で、ローカル アカウントと外部 (ソーシャル) アカウントが組み合わせされるようになりました。 ユーザーは、どちらのアカウントでもサインインできます。

ヒント

ユーザーにアプリにローカル アカウントを追加することをお勧めします。 このアプローチは、ソーシャル ログイン認証サービスがダウンした場合や、ソーシャル アカウントへのアクセスが失われる場合に、継続的なアクセスを確保するのに役立ちます。

サイトにユーザーが存在するようになった後で、アカウントの確認を有効にする

既存のユーザーが存在するサイトでアカウントの確認を有効にした場合、アカウントが確認されないため、アカウントをロックアウトします。

既存のユーザー ロックアウトの問題を回避するには、次のいずれかの方法を使用します。

  • データベースを更新して、既存のすべてのユーザーを確認済みとしてマークします。

  • 既存のユーザーを確認します。 たとえば、確認リンクを含むメールを一括送信します。

前提条件

.NET Core 3.0 以降の SDK

認証を備えた Web アプリを作成してテストする

認証機能を備えた Web アプリを作成するには、次のコマンドを実行します。

dotnet new webapp -au Individual -uld -o WebPWrecover
cd WebPWrecover
dotnet run

アプリを実行し、[登録] リンクを選んで、ユーザーを登録します。 登録が済むと、メールの確認をシミュレートするためのリンクが含まれる /Identity/Account/RegisterConfirmation ページにリダイレクトされます。

  • Click here to confirm your account リンクを選択します。
  • [ログイン] リンクを選び、同じ資格情報でサインインします。
  • Hello YourEmail@provider.com! リンクを選ぶと、/Identity/Account/Manage/PersonalData ページにリダイレクトされます。
  • 左側の [個人データ] タブを選び、[削除] を選びます。

メール プロバイダーを構成する

このチュートリアルでは、SendGrid を使ってメールを送信します。 他のメール プロバイダーを使ってもかまいません。 SendGrid または別のメール サービスを使ってメールを送信することをお勧めします。 SMTP では、メールがスパムとマークされないように構成することは困難です。

SendGrid アカウントでは、送信者の追加が必要になる場合があります。

セキュリティ保護されたメール キーをフェッチするためのクラスを作成します。 このサンプルでは、Services/AuthMessageSenderOptions.cs を作成します。

namespace WebPWrecover.Services;

public class AuthMessageSenderOptions
{
    public string? SendGridKey { get; set; }
}

SendGrid のユーザー シークレットを構成する

secret-manager ツールを使用して、SendGridKeyを設定します。 次に例を示します。

dotnet user-secrets set SendGridKey <SG.key>

Successfully saved SendGridKey = SG.keyVal to the secret store.

Windows では、シークレット マネージャーによって、secrets.json ディレクトリの %APPDATA%/Microsoft/UserSecrets/<WebAppName-userSecretsId> ファイルに、キーと値のペアが格納されます。

secrets.json ファイルの内容は暗号化されていません。 次のマークアップは secrets.json ファイルを示しています。 SendGridKey の値は削除されています。

{
  "SendGridKey": "<key removed>"
}

詳しくは、オプション パターン構成に関するページをご覧ください。

SendGrid をインストールする

このチュートリアルでは、SendGrid によるメール通知を追加する方法を示しますが、SMTP や他のメカニズムを使ってメールを送信することもできます。

SendGrid NuGet パッケージをインストールします。

パッケージ マネージャー コンソールから、次のコマンドを入力します。

Install-Package SendGrid

無料の SendGrid アカウントに登録するには、「SendGrid を無料で使い始める」をご覧ください。

IEmailSender を実装する

IEmailSender を実装するには、次のようなコードを使用して、Services/EmailSender.cs を作成します。

using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.Extensions.Options;
using SendGrid;
using SendGrid.Helpers.Mail;

namespace WebPWrecover.Services;

public class EmailSender : IEmailSender
{
    private readonly ILogger _logger;

    public EmailSender(IOptions<AuthMessageSenderOptions> optionsAccessor,
                       ILogger<EmailSender> logger)
    {
        Options = optionsAccessor.Value;
        _logger = logger;
    }

    public AuthMessageSenderOptions Options { get; } //Set with Secret Manager.

    public async Task SendEmailAsync(string toEmail, string subject, string message)
    {
        if (string.IsNullOrEmpty(Options.SendGridKey))
        {
            throw new Exception("Null SendGridKey");
        }
        await Execute(Options.SendGridKey, subject, message, toEmail);
    }

    public async Task Execute(string apiKey, string subject, string message, string toEmail)
    {
        var client = new SendGridClient(apiKey);
        var msg = new SendGridMessage()
        {
            From = new EmailAddress("Joe@contoso.com", "Password Recovery"),
            Subject = subject,
            PlainTextContent = message,
            HtmlContent = message
        };
        msg.AddTo(new EmailAddress(toEmail));

        // Disable click tracking.
        // See https://sendgrid.com/docs/User_Guide/Settings/tracking.html
        msg.SetClickTracking(false, false);
        var response = await client.SendEmailAsync(msg);
        _logger.LogInformation(response.IsSuccessStatusCode 
                               ? $"Email to {toEmail} queued successfully!"
                               : $"Failure Email to {toEmail}");
    }
}

メールをサポートするためのスタートアップを構成する

ConfigureServices ファイルの Startup.cs メソッドに次のコードを追加します。

  • 一時的なサービスとして EmailSender を追加します。
  • AuthMessageSenderOptions 構成インスタンスを登録します。
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.EntityFrameworkCore;
using WebPWrecover.Data;
using WebPWrecover.Services;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlite(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();

builder.Services.AddTransient<IEmailSender, EmailSender>();
builder.Services.Configure<AuthMessageSenderOptions>(builder.Configuration);

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.MapRazorPages();

app.Run();

RegisterConfirmation をスキャフォールディングします

Scaffold Identity の手順に従って、Account\RegisterConfirmation をスキャフォールドします。

Account.RegisterConfirmation がスキャフォールディングされている場合に既定のアカウント検証を無効にする

Account.RegisterConfirmation がスキャフォールディングされている場合は、このセクションの手順をすべて実行してください。

Important

Account.RegisterConfirmation がスキャフォールディングされていない場合は、次の手順をスキップして、次のセクションに進んでください。

ユーザーは /Identity/Account/RegisterConfirmation ページにリダイレクトされ、アカウントを確認するためのリンクを選択できます。 既定の Account.RegisterConfirmation は、テスト にのみ 使用されます。 運用アプリでは、自動アカウント検証を無効にする必要があります。

確認済みのアカウントを要求し、登録時にすぐにサインインできないようにするには、スキャフォールディングされた Identity ファイルでを設定します。

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable

using System;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;

namespace WebPWrecover.Areas.Identity.Pages.Account
{
    [AllowAnonymous]
    public class RegisterConfirmationModel : PageModel
    {
        private readonly UserManager<IdentityUser> _userManager;
        private readonly IEmailSender _sender;

        public RegisterConfirmationModel(UserManager<IdentityUser> userManager, IEmailSender sender)
        {
            _userManager = userManager;
            _sender = sender;
        }

        /// <summary>
        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
        ///     directly from your code. This API may change or be removed in future releases.
        /// </summary>
        public string Email { get; set; }

        /// <summary>
        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
        ///     directly from your code. This API may change or be removed in future releases.
        /// </summary>
        public bool DisplayConfirmAccountLink { get; set; }

        /// <summary>
        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
        ///     directly from your code. This API may change or be removed in future releases.
        /// </summary>
        public string EmailConfirmationUrl { get; set; }

        public async Task<IActionResult> OnGetAsync(string email, string returnUrl = null)
        {
            if (email == null)
            {
                return RedirectToPage("/Index");
            }
            returnUrl = returnUrl ?? Url.Content("~/");

            var user = await _userManager.FindByEmailAsync(email);
            if (user == null)
            {
                return NotFound($"Unable to load user with email '{email}'.");
            }

            Email = email;
            // Once you add a real email sender, you should remove this code that lets you confirm the account
            DisplayConfirmAccountLink = false;
            if (DisplayConfirmAccountLink)
            {
                var userId = await _userManager.GetUserIdAsync(user);
                var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
                EmailConfirmationUrl = Url.Page(
                    "/Account/ConfirmEmail",
                    pageHandler: null,
                    values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl },
                    protocol: Request.Scheme);
            }

            return Page();
        }
    }
}

この手順は、 Account.RegisterConfirmation がスキャフォールディングされている場合にのみ必要です。

スキャフォールディングされていない RegisterConfirmation は、 IEmailSender が実装され、 依存関係挿入コンテナーに登録されたときに自動的に検出します。

登録し、メールを確認し、パスワードをリセットする

Web アプリを実行し、アカウント確認とパスワード回復のフローをテストします。

  • アプリを実行して新しいユーザーを登録します
  • アカウント確認用のリンクが記載されたメールをご確認ください。 メールを受信しない場合は、「メールをデバッグする」をご覧ください。
  • リンクをクリックして、メールを確認します。
  • メールとパスワードを使ってサインインします。
  • サインアウトします。

パスワードのリセットをテストする

  • サインインしている場合は、[ログアウト] を選びます。
  • [ログイン] リンクを選び、[パスワードを忘れた場合] リンクを選びます。
  • アカウントの登録に使ったメール アドレスを入力します。
  • パスワードをリセットするためのリンクを含むメールが送信されます。 メールを確認し、リンクをクリックしてパスワードをリセットします。 パスワードが正常にリセットされたら、メール アドレスと新しいパスワードでサインインできます。

確認メールを再送信

.NET 5 以降で、[ログイン] ページの [電子メールの再送信] 確認リンクを選択します。

メールとアクティビティのタイムアウトを変更する

非アクティブ状態の既定のタイムアウトは 14 日です。 次のコードでは、非アクティブ状態のタイムアウトが 5 日に設定されます。

services.ConfigureApplicationCookie(o => {
    o.ExpireTimeSpan = TimeSpan.FromDays(5);
    o.SlidingExpiration = true;
});

すべてのデータ保護トークンの有効期限を変更する

次のコードでは、すべてのデータ保護トークンのタイムアウト期間が 3 時間に変更されます。

public void ConfigureServices(IServiceCollection services)
{

    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));
    services.AddDefaultIdentity<IdentityUser>(
                  options => options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<ApplicationDbContext>();

    services.Configure<DataProtectionTokenProviderOptions>(o =>
       o.TokenLifespan = TimeSpan.FromHours(3));

    services.AddTransient<IEmailSender, EmailSender>();
    services.Configure<AuthMessageSenderOptions>(Configuration);

    services.AddRazorPages();
}

組み込みの Identity ユーザー トークン (AspNetCore/src/Identity/Extensions.Core/src/TokenOptions.cs を参照) のタイムアウトは 1 日です。

メール トークンの有効期間を変更する

Identity ユーザー トークンの既定のトークン有効期間は 1 日です。 このセクションでは、メール トークンの有効期間を変更する方法について説明します。

カスタムの DataProtectorTokenProvider<TUser>DataProtectionTokenProviderOptions を追加します。

public class CustomEmailConfirmationTokenProvider<TUser>
                                       : DataProtectorTokenProvider<TUser> where TUser : class
{
    public CustomEmailConfirmationTokenProvider(IDataProtectionProvider dataProtectionProvider,
        IOptions<EmailConfirmationTokenProviderOptions> options,
        ILogger<DataProtectorTokenProvider<TUser>> logger)
                                          : base(dataProtectionProvider, options, logger)
    {

    }
}
public class EmailConfirmationTokenProviderOptions : DataProtectionTokenProviderOptions
{
    public EmailConfirmationTokenProviderOptions()
    {
        Name = "EmailDataProtectorTokenProvider";
        TokenLifespan = TimeSpan.FromHours(4);
    }
}

カスタム プロバイダーをサービス コンテナーに追加します。

public void ConfigureServices(IServiceCollection services)
{

    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));
    services.AddDefaultIdentity<IdentityUser>(config =>
    {
        config.SignIn.RequireConfirmedEmail = true;
        config.Tokens.ProviderMap.Add("CustomEmailConfirmation",
            new TokenProviderDescriptor(
                typeof(CustomEmailConfirmationTokenProvider<IdentityUser>)));
        config.Tokens.EmailConfirmationTokenProvider = "CustomEmailConfirmation";
      }).AddEntityFrameworkStores<ApplicationDbContext>();

    services.AddTransient<CustomEmailConfirmationTokenProvider<IdentityUser>>();

    services.AddTransient<IEmailSender, EmailSender>();
    services.Configure<AuthMessageSenderOptions>(Configuration);

    services.AddRazorPages();
}

メールをデバッグする

メールが機能しない場合:

  • EmailSender.Execute にブレークポイントを設定して、SendGridClient.SendEmailAsync が呼び出されることを確認します。
  • メールを送信するコンソール アプリを、EmailSender.Executeと同様のコードを使用して作成します。
  • メール アクティビティ ページを確認します。
  • 迷惑メール フォルダーを確認します。
  • 別のメール プロバイダー (Microsoft、Yahoo、Gmail など) で別のメール エイリアスを試します。
  • 別のメール アカウントに送信してみます。

セキュリティのベスト プラクティスは、テストと開発では運用用のシークレットを使用しないことです。 アプリを Azure に発行する場合は、Azure Web アプリ ポータルで SendGrid シークレットをアプリケーション設定として設定します。 構成システムは、環境変数からキーを読み取るように設定されています。

ソーシャルとローカルのログイン アカウントを結合する

このセクションを完了するには、最初に外部認証プロバイダーを有効にする必要があります。 Facebook、Google、外部プロバイダーの認証に関するページをご覧ください。

メールのリンクをクリックして、ローカル アカウントとソーシャル アカウントを組み合わせることができます。 次のシーケンスでは、"RickAndMSFT@gmail.com" は最初にローカル ログインとして作成されています。ただし、アカウントを最初にソーシャル ログインとして作成してから、ローカル ログインを追加することもできます。

Web アプリケーション: ユーザー認証済みの RickAndMSFT@gmail.com

[管理] リンクをクリックします。 このアカウントに関連付けられている外部 (ソーシャル ログイン) が 0 であることに注意してください。

ビューの管理

別のログイン サービスへのリンクをクリックし、アプリの要求を受け入れます。 次の図では、Facebook が外部認証プロバイダーです。

Facebook が一覧表示される外部ログイン ビューを管理する

2 つのアカウントが結合されています。 いずれかのアカウントでサインインできます。 ソーシャル ログイン認証サービスがダウンしたときのため、またはさらに可能性が高いのはソーシャル アカウントにアクセスできなくなったときのために、ユーザーにローカル アカウントを追加させたいことがあります。

サイトにユーザーが存在するようになった後で、アカウントの確認を有効にする

ユーザーがいるサイトでアカウントの確認を有効にすると、すべての既存のユーザーがロックアウトされます。 既存のユーザーは、アカウントが確認されていないためにロックアウトされます。 既存のユーザーのロックアウトを回避するには、次のいずれかの方法を使用します。

  • データベースを更新して、既存のすべてのユーザーを確認済みとしてマークします。
  • 既存のユーザーを確認します。 たとえば、確認リンクを含むメールを一括送信します。