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

この記事では、メールの確認とパスワードの回復を備えた ASP.NET Core Blazor Web アプリを構築する方法について説明します。

名前空間

この記事の例で使用されているアプリの名前空間は BlazorSample です。 アプリの名前空間を使用するようにコードの例を更新します。

メール プロバイダーを選択して構成する

この記事では、Mailchimp のトランザクション API を使用して Mandrill.net 経由でメールを送信します。 メール送信には、SMTP ではなく、メール サービスを使うことをお勧めします。 SMTP を適切に構成してセキュリティで保護することは困難です。 どのメール サービスを使用する場合でも、.NET アプリのガイダンスにアクセスし、アカウントを作成し、サービスの API キーを構成し、必要な NuGet パッケージをインストールします。

セキュリティ保護されたメール API キーをフェッチするためのクラスを作成します。 この記事の例では、キーを保持するために EmailAuthKey プロパティを指定した AuthMessageSenderOptions という名前のクラスを使用します。

AuthMessageSenderOptions:

namespace BlazorSample;

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

Program ファイルに AuthMessageSenderOptions 構成インスタンスを登録します。

builder.Services.Configure<AuthMessageSenderOptions>(builder.Configuration);

プロバイダーのセキュリティ キーのユーザー シークレットを構成する

シークレット マネージャー ツールを使用してキーを設定します。 次の例では、キー名は EmailAuthKey で、キーは {KEY} プレースホルダーで表されます。 コマンド シェルで、アプリのルート フォルダーに移動し、API キーを使用して次のコマンドを実行します。

dotnet user-secrets set "EmailAuthKey" "{KEY}"

詳細については、「ASP.NET Core での開発におけるアプリ シークレットの安全な保存」を参照してください。

IEmailSender を実装する

プロバイダー用に IEmailSender を実装します。 次の例は、Mandrill.net を使用する Mailchimp のトランザクション API に基づいています。 別のプロバイダーについては、Execute メソッドでメッセージ送信を実装する方法に関するドキュメントを参照してください。

Components/Account/EmailSender.cs:

using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using Mandrill;
using Mandrill.Model;
using BlazorSample.Data;

namespace BlazorSample.Components.Account;

public class EmailSender(IOptions<AuthMessageSenderOptions> optionsAccessor,
    ILogger<EmailSender> logger) : IEmailSender<ApplicationUser>
{
    private readonly ILogger logger = logger;

    public AuthMessageSenderOptions Options { get; } = optionsAccessor.Value;

    public Task SendConfirmationLinkAsync(ApplicationUser user, string email, 
        string confirmationLink) => SendEmailAsync(email, "Confirm your email", 
        $"Please confirm your account by " +
        "<a href='{confirmationLink}'>clicking here</a>.");

    public Task SendPasswordResetLinkAsync(ApplicationUser user, string email, 
        string resetLink) => SendEmailAsync(email, "Reset your password", 
        $"Please reset your password by <a href='{resetLink}'>clicking here</a>.");

    public Task SendPasswordResetCodeAsync(ApplicationUser user, string email, 
        string resetCode) => SendEmailAsync(email, "Reset your password", 
        $"Please reset your password using the following code: {resetCode}");

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

        await Execute(Options.EmailAuthKey, subject, message, toEmail);
    }

    public async Task Execute(string apiKey, string subject, string message, 
        string toEmail)
    {
        var api = new MandrillApi(apiKey);
        var mandrillMessage = new MandrillMessage("sarah@contoso.com", toEmail, 
            subject, message);
        await api.Messages.SendAsync(mandrillMessage);

        logger.LogInformation("Email to {EmailAddress} sent!", toEmail);
    }
}

Note

メッセージの本文コンテンツには、メール サービス プロバイダーの特別なエンコードが必要な場合があります。 メッセージ本文のリンクをたどれない場合は、サービス プロバイダーのドキュメントを参照してください。

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

Program ファイルで、メール送信者の実装を EmailSender に変更します。

- builder.Services.AddSingleton<IEmailSender<ApplicationUser>, IdentityNoOpEmailSender>();
+ builder.Services.AddSingleton<IEmailSender<ApplicationUser>, EmailSender>();

アプリから IdentityNoOpEmailSender (Components/Account/IdentityNoOpEmailSender.cs) を削除します。

RegisterConfirmation コンポーネント (Components/Account/Pages/RegisterConfirmation.razor) で、EmailSenderIdentityNoOpEmailSender であるかをチェックする @code ブロックの条件付きブロックを削除します。

- else if (EmailSender is IdentityNoOpEmailSender)
- {
-     ...
- }

また、RegisterConfirmation コンポーネントで、emailConfirmationLink フィールドをチェックするための Razor マークアップとコードを削除し、メールをチェックするようにユーザーに指示する行だけを残します。

- @if (emailConfirmationLink is not null)
- {
-     ...
- }
- else
- {
     <p>Please check your email to confirm your account.</p>
- }

@code {
-    private string? emailConfirmationLink;

     ...
}

メールとアクティビティのタイムアウト

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

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

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

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

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

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

Note

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

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

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

Note

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

メール トークンの有効期間を変更するには、カスタムの DataProtectorTokenProvider<TUser>DataProtectionTokenProviderOptions を追加します。

CustomTokenProvider.cs:

using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;

namespace BlazorSample;

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 class CustomPasswordResetTokenProvider<TUser> 
    : DataProtectorTokenProvider<TUser> where TUser : class
{
    public CustomPasswordResetTokenProvider(
        IDataProtectionProvider dataProtectionProvider,
        IOptions<PasswordResetTokenProviderOptions> options,
        ILogger<DataProtectorTokenProvider<TUser>> logger)
        : base(dataProtectionProvider, options, logger)
    {
    }
}

public class PasswordResetTokenProviderOptions : 
    DataProtectionTokenProviderOptions
{
    public PasswordResetTokenProviderOptions()
    {
        Name = "PasswordResetDataProtectorTokenProvider";
        TokenLifespan = TimeSpan.FromHours(3);
    }
}

Program ファイルでカスタム トークン プロバイダーを使用するようにサービスを構成します。

builder.Services.AddIdentityCore<ApplicationUser>(options =>
    {
        options.SignIn.RequireConfirmedAccount = true;
        options.Tokens.ProviderMap.Add("CustomEmailConfirmation",
            new TokenProviderDescriptor(
                typeof(CustomEmailConfirmationTokenProvider<ApplicationUser>)));
        options.Tokens.EmailConfirmationTokenProvider = 
            "CustomEmailConfirmation";
    })
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddSignInManager()
    .AddDefaultTokenProviders();

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

トラブルシューティング

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

  • EmailSender.Execute にブレークポイントを設定して、SendEmailAsync が呼び出されることを確認します。
  • 問題をデバッグするために、EmailSender.Execute のようなコードを使用してメールを送信するコンソール アプリを作成します。
  • メール プロバイダーの Web サイトでアカウントのメール履歴ページを確認します。
  • メッセージがないか迷惑メール フォルダーを確認します。
  • 別のメール プロバイダー (Microsoft、Yahoo、Gmail など) で別のメール エイリアスを試します。
  • 別のメール アカウントに送信してみます。

警告

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

サイトにユーザーがいる用になった後でアカウントの確認を有効にする

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

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

その他のリソース