ASP.NET Core Blazor でのアカウントの確認とパスワードの回復
Note
これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の .NET 9 バージョンを参照してください。
重要
この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft はここに示されている情報について、明示か黙示かを問わず、一切保証しません。
現在のリリースについては、この記事の .NET 9 バージョンを参照してください。
この記事では、メールの確認とパスワードの回復を備えた ASP.NET Core Blazor Web App を構築する方法について説明します。
Note
この記事は、 Blazor Web Appにのみ適用されます。 ASP.NET Core Blazor WebAssembly を使用したスタンドアロン Identity アプリの電子メール確認とパスワード回復を実装するには、ASP.NET Core Blazor WebAssemblyを使用した ASP.NET Core Identityでのアカウントの確認とパスワードの回復に関するを参照してください。
名前空間
この記事の例で使用されているアプリの名前空間は BlazorSample
です。 アプリの名前空間を使用するようにコードの例を更新します。
メール プロバイダーを選択して構成する
この記事では、Mailchimp のトランザクション API を使用して Mandrill.net 経由でメールを送信します。 メール送信には、SMTP ではなく、メール サービスを使うことをお勧めします。 SMTP を適切に構成してセキュリティで保護することは困難です。 どのメール サービスを使用する場合でも、.NET アプリのガイダンスにアクセスし、アカウントを作成し、サービスの API キーを構成し、必要な NuGet パッケージをインストールします。
シークレット電子メール プロバイダー API キーを保持するクラスを作成します。 この記事の例では、AuthMessageSenderOptions
プロパティを持つ EmailAuthKey
という名前のクラスを使用してキーを保持します。
AuthMessageSenderOptions.cs
:
namespace BlazorSample;
public class AuthMessageSenderOptions
{
public string? EmailAuthKey { get; set; }
}
AuthMessageSenderOptions
ファイルに Program
構成インスタンスを登録します。
builder.Services.Configure<AuthMessageSenderOptions>(builder.Configuration);
電子メール プロバイダーのセキュリティ キーのシークレットを構成する
プロバイダーから電子メール プロバイダーのセキュリティ キーを受け取り、次のガイダンスで使用します。
次のいずれかの方法または両方の方法を使用して、シークレットをアプリに提供します。
- Secret Manager ツール: シークレット マネージャー ツールは、ローカル コンピューターにプライベート データを格納し、ローカル開発時にのみ使用します。
- Azure Key Vault: ローカルで作業するときに開発環境を含め、任意の環境で使用するためにシークレットをキー コンテナーに格納できます。 一部の開発者は、ステージングおよび運用環境のデプロイにキー コンテナーを使用し、ローカル開発に Secret Manager ツールを使用することを好みます。
プロジェクト コードまたは構成ファイルにシークレットを格納しないことを強くお勧めします。 このセクションの方法のいずれかまたは両方など、セキュリティで保護された認証フローを使用します。
シークレット マネージャー ツール
プロジェクトが既に Secret Manager ツール用に初期化されている場合プロジェクト ファイル (<AppSecretsId>
) にアプリ シークレット識別子 (.csproj
) が既に存在します。 Visual Studio では、ソリューション エクスプローラーでプロジェクトが選択されているときに Properties パネルを見て、アプリ シークレット ID が存在するかどうかを確認できます。 アプリが初期化されていない場合は、プロジェクトのディレクトリに対して開かれたコマンド シェルで次のコマンドを実行します。 Visual Studio では、開発者 PowerShell コマンド プロンプトを使用できます。
dotnet user-secrets init
Secret Manager ツールを使用して API キーを設定します。 次の例では、キー名はEmailAuthKey
と一致するようにAuthMessageSenderOptions.EmailAuthKey
され、キーは {KEY}
プレースホルダーで表されます。 API キーを使用して次のコマンドを実行します。
dotnet user-secrets set "EmailAuthKey" "{KEY}"
Visual Studio を使用している場合は、ソリューション エクスプローラーでサーバー プロジェクトを右クリックし、[ユーザー シークレットの管理] 選択することで、シークレットが設定されていることを確認。
詳細については、「ASP.NET Core での開発におけるアプリ シークレットの安全な保存」を参照してください。
警告
アプリ シークレット、接続文字列、資格情報、パスワード、個人識別番号 (PIN)、プライベート C#/.NET コード、秘密キー/トークンをクライアント側コードに格納しないでください。これは安全ではありません。 テスト/ステージング環境と運用環境では、サーバー側の Blazor コードと Web API は、プロジェクト コードまたは構成ファイル内で資格情報を維持しないように、セキュリティで保護された認証フローを使用する必要があります。 ローカル開発テスト以外では、環境変数が最も安全なアプローチではないため、環境変数を使用して機密データを格納しないようにすることをお勧めします。 ローカル開発テストでは、機密データをセキュリティで保護するために、 Secret Manager ツール をお勧めします。 詳細については、「 機密データと資格情報を安全に管理するを参照してください。
Azure Key Vault
azure Key Vault は、アプリのクライアント シークレットをアプリに提供するための安全なアプローチを提供します。
キー コンテナーを作成してシークレットを設定するには、「Azure Key Vault シークレットについて (Azure ドキュメント)」を参照してください。Azure Key Vault の使用を開始するためにリソースをクロスリンクします。 このセクションのコードを実装するには、キー コンテナーとシークレットを作成するときに、Azure のキー コンテナー URI とシークレット名を記録します。 [アクセス ポリシー] パネルでシークレットのアクセス ポリシーを設定する場合:
- シークレット アクセス許可の取得のみが必要です。
- シークレットの プリンシパル としてアプリケーションを選択します。
Azure または Entra ポータルで、電子メール プロバイダー キー用に作成したシークレットへのアクセス権がアプリに付与されていることを確認します。
重要
Key Vault のシークレットは、有効期限が設定された状態で作成されます。 キー ボルトのシークレットの有効期限が切れるタイミングをしっかりと追跡し、その日付が過ぎる前にアプリ用の新しいシークレットを作成してください。
次の AzureHelper
クラスをサーバー プロジェクトに追加します。 GetKeyVaultSecret
メソッドは、キー ボールトからシークレットを取得します。 プロジェクトの名前空間スキームに合わせて名前空間 (BlazorSample.Helpers
) を調整します。
Helpers/AzureHelper.cs
:
using Azure;
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
namespace BlazorSample.Helpers;
public static class AzureHelper
{
public static string GetKeyVaultSecret(string tenantId, string vaultUri, string secretName)
{
DefaultAzureCredentialOptions options = new()
{
// Specify the tenant ID to use the dev credentials when running the app locally
// in Visual Studio.
VisualStudioTenantId = tenantId,
SharedTokenCacheTenantId = tenantId
};
var client = new SecretClient(new Uri(vaultUri), new DefaultAzureCredential(options));
var secret = client.GetSecretAsync(secretName).Result;
return secret.Value.Value;
}
}
サービスがサーバー プロジェクトの Program
ファイルに登録されている場合は、Options 構成でシークレットを取得してバインドします。
var tenantId = builder.Configuration.GetValue<string>("AzureAd:TenantId")!;
var vaultUri = builder.Configuration.GetValue<string>("AzureAd:VaultUri")!;
var emailAuthKey = AzureHelper.GetKeyVaultSecret(
tenantId, vaultUri, "EmailAuthKey");
var authMessageSenderOptions =
new AuthMessageSenderOptions() { EmailAuthKey = emailAuthKey };
builder.Configuration.GetSection(authMessageSenderOptions.EmailAuthKey)
.Bind(authMessageSenderOptions);
上記のコードが動作する環境を制御する場合 (たとえば、ローカル開発に Secret Manager ツール を使用することを選択したためにコードをローカルで実行しないようにする場合は、環境をチェックする条件付きステートメントで上記のコードをラップできます。
if (!context.HostingEnvironment.IsDevelopment())
{
...
}
サーバー プロジェクトの appsettings.json
の AzureAd
セクションで、アプリの Entra ID TenantId
が存在することを確認し、次の VaultUri
構成キーと値を追加します (まだ存在しない場合)。
"VaultUri": "{VAULT URI}"
前の例では、{VAULT URI}
プレースホルダーはキー コンテナー URI です。 URI に末尾のスラッシュを含めます。
例:
"VaultUri": "https://contoso.vault.azure.net/"
構成は、アプリの環境構成ファイルに基づいて専用のキー コンテナーとシークレット名を簡単に指定するために使用されます。 たとえば、開発中の appsettings.Development.json
、ステージング時の appsettings.Staging.json
、運用デプロイの appsettings.Production.json
に対して、さまざまな構成値を指定できます。 詳細については、ASP.NET Core Blazor 構成を参照してください。
IEmailSender
を実装する
次の例は、Mandrill.net を使用する Mailchimp のトランザクション API に基づいています。 別のプロバイダーについては、電子メール メッセージの送信を実装する方法に関するドキュメントを参照してください。
Mandrill.net NuGet パッケージをプロジェクトに追加します。
次の EmailSender
クラスを追加して、 IEmailSender<TUser>を実装します。 次の例では、 ApplicationUser
は IdentityUserです。 メッセージ HTML マークアップをさらにカスタマイズできます。 message
に渡されるMandrillMessage
が<
文字で始まる限り、Mandrill.net API はメッセージ本文が HTML で構成されていることを前提としています。
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(AppUser user, string email,
string confirmationLink) => SendEmailAsync(email, "Confirm your email",
"<html lang=\"en\"><head></head><body>Please confirm your account by " +
$"<a href='{confirmationLink}'>clicking here</a>.</body></html>");
public Task SendPasswordResetLinkAsync(AppUser user, string email,
string resetLink) => SendEmailAsync(email, "Reset your password",
"<html lang=\"en\"><head></head><body>Please reset your password by " +
$"<a href='{resetLink}'>clicking here</a>.</body></html>");
public Task SendPasswordResetCodeAsync(AppUser user, string email,
string resetCode) => SendEmailAsync(email, "Reset your password",
"<html lang=\"en\"><head></head><body>Please reset your password " +
$"using the following code:<br>{resetCode}</body></html>");
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
) で、@code
が EmailSender
であるかをチェックする IdentityNoOpEmailSender
ブロックの条件付きブロックを削除します。
- else if (EmailSender is IdentityNoOpEmailSender)
- {
- ...
- }
また、RegisterConfirmation
コンポーネントで、Razor フィールドをチェックするための emailConfirmationLink
マークアップとコードを削除し、メールをチェックするようにユーザーに指示する行だけを残します。
- @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;
});
すべての ASP.NET Core データ保護トークンの有効期限を変更する
次のコードでは、データ保護トークンのタイムアウト期間が 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 など) で別のメール エイリアスを試します。
- 別のメール アカウントに送信してみます。
その他のリソース
ASP.NET Core