注
これは、この記事の最新バージョンではありません。 現在のリリースについては、 この記事の .NET 10 バージョンを参照してください。
この記事では、メール確認とパスワード回復、および ASP.NET Core Blazor Web App を構成する方法について説明します。
注
この記事は 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 ツール用に初期化されている場合は、プロジェクト ファイル (<UserSecretsId>) にユーザー シークレット識別子 (.csproj) が既に含まれています。 Visual Studio では、ソリューション エクスプローラーでプロジェクトが選択されているときに [プロパティ] パネルを見て、ユーザー シークレット ID が存在するかどうかを確認できます。 アプリが初期化されていない場合は、プロジェクトのディレクトリに対して開かれたコマンド シェルで次のコマンドを実行します。 Visual Studio では、開発者用 PowerShell コマンド プロンプトを使用できます。
dotnet user-secrets init
シークレット マネージャー ツールを使用して 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 の使用を開始するためにリソースをクロスリンクします。 このセクションの例では、シークレット名は "EmailAuthKey" です。
Entra または Azure portal でキー ボールトを設定する際:
Azure ロールベースのアクセス制御 (RBAC) を使用するようにキー・ボールトを構成します。 ローカルでの開発やテストを含め、 Azure Virtual Network で動作していない場合は、 ネットワーク ステップでのパブリック アクセスが 有効 になっていることを確認します (オン)。 パブリック アクセスを有効にすると、キー ボールト エンドポイントのみが露出されます。 認証されたアカウントは引き続きアクセスに必要です。
Identity ロールを使用して、Azure マネージド Identityを作成します (または、使用する予定の既存のマネージド にロールを追加します)。 デプロイをホストしている Azure App Service にマネージド Identity を割り当てます: Settings>Identity>User assigned>Add。
注
Azure CLI または Visual Studio の Azure サービス認証を使用して、Key Vault アクセスの承認されたユーザーを使用してアプリをローカルで実行する予定の場合は、Key Vault シークレット ユーザー ロールを使用して、Access Control (IAM) に開発者の Azure ユーザー アカウントを追加します。 Visual Studio で Azure CLI を使用する場合は、Developer PowerShell パネルから
az loginコマンドを実行し、プロンプトに従ってテナントで認証します。
このセクションのコードを実装するには、キー コンテナーとシークレットを作成するときに、Azure のキー コンテナー URI (例: "https://contoso.vault.azure.net/"、末尾にスラッシュが必要) とシークレット名 (例: "EmailAuthKey") を記録します。
重要
Key Vault のシークレットは、有効期限が設定された状態で作成されます。 キー ボルトのシークレットの有効期限が切れるタイミングをしっかりと追跡し、その日付が過ぎる前にアプリ用の新しいシークレットを作成してください。
次の AzureHelper クラスをサーバー プロジェクトに追加します。
GetKeyVaultSecret メソッドは、キー ボールトからシークレットを取得します。 プロジェクトの名前空間スキームに合わせて名前空間 (BlazorSample.Helpers) を調整します。
Helpers/AzureHelper.cs:
using Azure.Core;
using Azure.Security.KeyVault.Secrets;
namespace BlazorSample.Helpers;
public static class AzureHelper
{
public static string GetKeyVaultSecret(string vaultUri,
TokenCredential credential, string secretName)
{
var client = new SecretClient(new Uri(vaultUri), credential);
var secret = client.GetSecretAsync(secretName).Result;
return secret.Value.Value;
}
}
サービスがサーバー プロジェクトの Program ファイルに登録されている場合は、Options 構成でシークレットを取得してバインドします。
TokenCredential? credential;
if (builder.Environment.IsProduction())
{
credential = new ManagedIdentityCredential("{MANAGED IDENTITY CLIENT ID}");
}
else
{
// Local development and testing only
DefaultAzureCredentialOptions options = new()
{
// Specify the tenant ID to use the dev credentials when running the app locally
// in Visual Studio.
VisualStudioTenantId = "{TENANT ID}",
SharedTokenCacheTenantId = "{TENANT ID}"
};
credential = new DefaultAzureCredential(options);
}
var emailAuthKey = AzureHelper.GetKeyVaultSecret("{VAULT URI}", credential,
"EmailAuthKey");
var authMessageSenderOptions =
new AuthMessageSenderOptions() { EmailAuthKey = emailAuthKey };
builder.Configuration.GetSection(authMessageSenderOptions.EmailAuthKey)
.Bind(authMessageSenderOptions);
注
非運用環境では、前の例では、 DefaultAzureCredential を使用して認証を簡略化し、Azure ホスティング環境で使用される資格情報とローカル開発で使用される資格情報を組み合わせて Azure にデプロイするアプリを開発しています。 詳細については、「 システム割り当てマネージド ID を使用して Azure リソースに対して Azure でホストされる .NET アプリを認証する」を参照してください。
上記の例は、マネージド Identity クライアント ID ({MANAGED IDENTITY CLIENT ID})、ディレクトリ (テナント) ID ({TENANT ID})、キー コンテナー URI ({VAULT URI}、例: https://contoso.vault.azure.net/、末尾のスラッシュが必要) がハードコーディングされた値によって提供されることを意味します。 これらの値の一部または全部は、アプリ設定の構成から指定できます。 たとえば、次の例では、アプリ設定ファイルのAzureAd ノードからコンテナー URI を取得し、前の例のvaultUriの呼び出しでGetKeyVaultSecretを使用できます。
var vaultUri = builder.Configuration.GetValue<string>("AzureAd:VaultUri")!;
詳細については、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(ApplicationUser 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(ApplicationUser 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(ApplicationUser 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);
}
}
注
メッセージの本文コンテンツでは、メール サービス プロバイダーの特別なエンコードが必要になる場合があります。 メール メッセージで、メッセージ本文に含まれるリンクのリンク先に移動できない場合は、サービス プロバイダーのドキュメントを参照して問題のトラブルシューティングを行ってください。
メールをサポートするようにアプリを構成する
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 日でタイムアウトとなります。
注
通常、.NET 参照ソースへのドキュメント リンクを使用すると、リポジトリの既定のブランチが読み込まれます。このブランチは、.NET の次回リリースに向けて行われている現在の開発を表します。 特定のリリースのタグを選択するには、[Switch branches or tags](ブランチまたはタグの切り替え) ドロップダウン リストを使います。 詳細については、「ASP.NET Core ソース コードのバージョン タグを選択する方法」 (dotnet/AspNetCore.Docs #26205) を参照してください。
メール トークンの有効期間を変更する
Identityユーザー トークンの既定のトークン有効期間は 1 日です。
注
通常、.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