本記事は 2025年4月24日に公開された「Enforce Customer Email Verification in Your B2B Blazor Web App」を機械翻訳した記事です。
ユーザーがアプリケーションにサインアップする際の最初の手順は、入力されたメールアドレスにそのユーザーが実際にアクセスできるかを検証することです。メールアドレス検証は、ユーザーの身元を確認するための唯一のセキュリティ対策というわけではありませんが、スパムや不正行為の削減に貢献します。Auth0は、特定のユースケースに応じてメールアドレス検証を提供します。本記事ではこの機能を用いてBlazor Webアプリケーションへのサインアップ時に、メールアドレスを検証リンクで確認する方法を紹介します。
サンプルアプリケーション
以前の記事では、Blazorで実装されたB2B SaaSアプリケーションにサインアップする顧客のオンボーディングを管理する方法を紹介しました。その記事では、セルフサービスによる登録とコードによるOrganizationの作成に焦点を当てたかったため、顧客のメールアドレス検証は検討しませんでした。今回は、自己登録する顧客のメールアドレス検証ステップを追加し、アプリケーションを拡張します。
サンプルプロジェクトは、次のコマンドでダウンロードできます。
git clone --branch organization-via-code --single-branch https://github.com/andychiare/MyBlazorSaaS
READMEファイルの指示に従って、Auth0でアプリケーションを登録、設定します。Auth0アカウントがない場合は、無料でサインアップできます。
メールアドレス検証プロセス
標準の設定ではユーザーがサインアップするとAuth0は検証リンクを送信します。しかし、サインアップ時以外でもアプリケーションへのアクセスや特定のアクション実行を許可する前に、メールアドレスが検証済みかを確認するステップを独自に組み込み込めます。
このBlazorアプリケーションでは、次のメールアドレス検証プロセスを実装します。
- 顧客がサインアップすると、Auth0は自動的にユーザーにメールを送信し、アプリケーションはユーザーに受信トレイを確認するよう促すメッセージを表示します。
- メッセージを表示するページには、ユーザーが最初のメールを受信しなかった場合に新しい検証リンクを再送信するためのボタンがあります。
- ユーザーが他のアプリケーションページにアクセスしようとすると、アプリはアクセスを許可する前にユーザーのメールアドレスが検証済みかどうかを確認します。
- メールアドレスが検証されると、顧客は新しいサブスクリプションのためのOrganizationを作成できます。
メールアドレスの検証を待つ
Blazorアプリケーションに実装されている現在の顧客オンボーディングフローでは、ユーザーはサインアップ後すぐにOrganizationを作成できます。画面上では、サインアップフォームからOrganization作成フォームへ直接遷移します。顧客が新しいOrganizationを作成する前に、メールアドレスが検証済みであることを確認する必要があります。そのため、まず、前述のステップのうち1と2を満たすページを実装します。
検証待機ページ
MyBlazorSaaS/Components/Pages/Onboarding
フォルダに、Verify.razor
という名前の新しいファイルを作成し、次のコードを追加します。
<!-- MyBlazorSaaS/Components/Pages/Onboarding/Verify.razor --> @page "/Onboarding/Verify" @rendermode InteractiveServer @using Auth0.ManagementApi.Models @using System.Security.Claims @inject NavigationManager NavigationManager @inject IAuth0Management auth0Management <h1>Verify your email</h1> <p> Please check your inbox for a verification link to continue creating your account. </p> <div> <button class="btn btn-primary" @onclick="ResendVerificationEmail">Resend Verification</button> </div> <div> @Message </div> @code { [CascadingParameter] private Task<AuthenticationState>? authenticationState { get; set; } private string Message = ""; private async Task ResendVerificationEmail() { if (authenticationState is not null) { var state = await authenticationState; var userId = state?.User?.FindFirst(c => c.Type == ClaimTypes.NameIdentifier)?.Value; try { var managementClient = await auth0Management.getClient(); await managementClient.Jobs.SendVerificationEmailAsync(new VerifyEmailJobRequest() { UserId = userId }); Message = "The verification e-mail has successfully been sent. Please check your inbox"; } catch (Exception) { Message = "Failed to resend verification e-mail."; } } else { NavigationManager.NavigateTo("/Onboarding/Signup"); } } }
このページは、ユーザーに受信トレイを確認するよう促すメッセージと、必要に応じて検証リンクを再送信するボタンを表示します。
現在のオンボーディングフローでは、ユーザーのサインアップ後にOrganization作成ページが表示されるため、フローを変更する必要があります。MyBlazorSaaS
フォルダ内の Program.cs
ファイルを開き、以下に示すコードを置き換えます。
// MyBlazorSaaS/Program.cs //...existing code... app.MapGet("/account/signup", async (HttpContext httpContext, string login_hint) => { var authenticationProperties = new LoginAuthenticationPropertiesBuilder() //.WithRedirectUri("/onboarding/createOrganization") // 👈 old code .WithRedirectUri("/onboarding/verify") // 👈 new code .WithParameter("screen_hint", "signup") .WithParameter("login_hint", login_hint) .Build(); await httpContext.ChallengeAsync("OnboardingScheme", authenticationProperties); }); //...existing code...
これにより、ユーザーはOrganization作成ページではなく、検証待機ページにリダイレクトされます。
検証リンクの再送信
検証待機ページには「Resend Verification」ボタンが表示されます。このボタンをクリックすると下記のコードが実行されます。
<!-- MyBlazorSaaS/Components/Pages/Onboarding/Verify.razor --> //...existing code... @code { [CascadingParameter] private Task<AuthenticationState>? authenticationState { get; set; } private string Message = ""; private async Task ResendVerificationEmail() { if (authenticationState is not null) { var state = await authenticationState; var userId = state?.User?.FindFirst(c => c.Type == ClaimTypes.NameIdentifier)?.Value; try { var managementClient = await auth0Management.getClient(); await managementClient.Jobs.SendVerificationEmailAsync(new VerifyEmailJobRequest() { UserId = userId }); Message = "The verification e-mail has successfully been sent. Please check your inbox"; } catch (Exception) { Message = "Failed to resend verification e-mail."; } } else { NavigationManager.NavigateTo("/Onboarding/Signup"); } } }
ResendVerificationEmail()
メソッドは認証状態からユーザーIDを取得します。その後、管理クライアントのインスタンスを取得し、ユーザーIDを持つ VerifyEmailJobRequest
のインスタンスを渡して SendVerificationEmailAsync()
メソッドを実行します。Management APIクライアントが提供するこのメソッドは、メールアドレス検証エンドポイントを呼び出すことに相当します。これにより、新しいチケットが生成され、検証リンクに含められてユーザーにメールで送信されます。
メールアドレス検証テンプレートのカスタマイズ
ユーザーに送信される検証リンク付きのメールメッセージは標準で次のように設定されています。
このメッセージはAuth0ダッシュボードでカスタマイズできます。Branding/ブランディング > Email Templates/メールテンプレートページへ移動し、Verification Email (Link)/確認メール(リンク) テンプレートを選択して変更を加えます。送信者アドレス、件名、メッセージ本文はHTMLLiquid構文を使ってカスタマイズできます。他の設定も変更でき、また、共通変数を使用してコンテキストに応じた値をメッセージに含めるようなカスタマイズが可能です。メールテンプレートのカスタマイズについてさらに学ぶには、ドキュメントを確認ください。
標準の設定では、ユーザーが検証リンクをクリックしてもアプリケーション上で目に見える変化は何も起こりません。Auth0はユーザーのメールフィールドを検証済みとしてマークし、次の画像で確認できるように、メールアドレス検証プロセスが成功したことを確認するページを表示します。
しかし、セルフサブスクリプションフローでは、メールアドレスが検証された後、ユーザーを次のオンボーディングステップであるOrganization作成画面にリダイレクトしたいと考えます。メールアドレス検証テンプレートには Redirect To フィールドがあり、メールアドレス検証後にユーザーが遷移するページのURLを指定できます。前述の共通変数のいずれかを使用してURLをパラメータ化できます。具体的には、Redirect To フィールドに次の値を入力し、Save ボタンをクリックします。
{{ application.callback_domain }}/onboarding/createOrganization
これで完了です。メールアドレス検証後、ユーザーはOrganization作成ページにリダイレクトされます。
A new Auth0-powered .NET app in less than a minute? Try out our templates
Install the Nugget package
メールアドレス検証の要求
顧客オンボーディングフローを完了させるには、あと1つ要素が不足しています。メールアドレスがまだ検証されていない場合に、アプリケーションの保護されたページへアクセスできないようにする必要があります。つまり、ユーザーが認証済みか確認する際に、メールアドレスも同じく検証済みであることを確認する必要があるということです。
基本的なアプローチ
ユーザーが Home.razor
ページ、Signup.razor
ページ、Verify.razor
ページにアクセスする際には、メールアドレスが検証済みか確認する必要はありません。最初の2つは公開ページで、3番目のページはまだメールアドレスを検証していないユーザー向けに設計されているためです。
CreateOrganization.razor
ページでは、ユーザーのメールアドレスの有効性を確認する必要があります。そのために、CreateOrganization.razor
ページに、本記事で強調表示されている変更を適用します。
<!-- MyBlazorSaaS/Components/Pages/Onboarding/CreateOrganization.razor --> //...existing code... protected override async Task OnInitializedAsync() { if (authenticationState is not null) { var state = await authenticationState; // 👇 new code var isEmailVerified = state.User?.FindFirst(c => c.Type == "email_verified")?.Value?.ToLowerInvariant() == "true"; // ☝️ new code // 👇 changed code if (state.User?.Identity?.IsAuthenticated??false && isEmailVerified) // ☝️ changed code { Email = state.User?.FindFirst(c => c.Type == ClaimTypes.Email)?.Value??String.Empty; UserId = state.User?.FindFirst(c => c.Type == ClaimTypes.NameIdentifier)?.Value??String.Empty; } else { NavigationManager.NavigateTo("/Onboarding/Signup"); } } } //...existing code...
ここでは新しい変数 isEmailVerified
が導入されたことを確認できます。この変数は、email_verified
クレームの値からBool型の値を取得します。Organization作成ステップに進むための追加条件として、この変数を使用します。
より構造化されたアプローチ
さて、アプリケーションの保護されたページの多くにもメールアドレス検証チェックを適用する必要があります。もちろん、どのページに適用するかはアプリケーションによります。この例では、サーバー側の Weather.razor
ページと、クライアント側の Counter.razor
および FetchData.razor
ページにも同じチェックを適用します。
このような場合、前述のアプローチでも目的を達成できますが、コードの重複とメンテナンスの観点からは非効率です。代わりにASP.NET Coreポリシーベースの認可を使用します。
まずMyBlazorSaaS
フォルダ内の Program.cs
ファイルを開き、以下に強調表示されているコードを追加します。
// MyBlazorSaaS/Program.cs //... existing code... // 👇 new code builder.Services .AddAuthorization(options => { options.AddPolicy("VerifiedEmail", policy => policy.RequireAssertion(context => context.User.HasClaim(claim => ( claim.Type == "email_verified" && claim.Value?.ToLowerInvariant() == "true") ) ) ); }); // ☝️ new code // Add services to the container. builder.Services.AddRazorComponents() .AddInteractiveServerComponents() .AddInteractiveWebAssemblyComponents() .AddAuthenticationStateSerialization(); //... existing code...
このコードは、"VerifiedEmail"
という名前の認可ポリシーを定義します。このポリシーは、現在のユーザーが email_verified
クレームを true
に設定しているかどうかを確認します。これはAuth0が発行するIDトークンに含まれる標準クレームです。
次に、必要な認可なしにページへアクセスしようとしたときにユーザーがリダイレクトされるページを作成します。デフォルトでは、Blazorは未認可のユーザーを Account/AccessDenied
ページにリダイレクトします。そこで、MyBlazorSaaS/Components/Pages
フォルダに Account
フォルダを作成し、次のコードで AccessDenied.razor
ページを追加します。
<!-- MyBlazorSaaS/Components/Pages/Account/AccessDenied.razor --> @page "/account/accessdenied" <PageTitle>Access Denied</PageTitle> <h2>You are not authorized to access this page</h2> <p>@Reason</p> @code { [CascadingParameter] private Task<AuthenticationState>? authenticationState { get; set; } private string Reason = ""; protected override async Task OnInitializedAsync() { if (authenticationState is not null) { var state = await authenticationState; var isEmailVerified = state.User?.FindFirst(c => c.Type == "email_verified")?.Value?.ToLowerInvariant() == "true"; if (state.User?.Identity?.IsAuthenticated??false && isEmailVerified) { Reason = "Your email is not verified yet!"; } } } }
このページは、未認可のユーザーへ一般的なメッセージを表示し、実際の理由に関する具体的なメッセージを表示することを試みます。具体的には、ユーザーが email_verified
クレームを true
に設定しているか確認し、状況に応じたメッセージを表示します。
最後に、保護されたページの Authorize
属性に VerifiedEmail
ポリシーを以下のように指定します。
<!-- MyBlazorSaaS/Components/Pages/Weather.razor --> @page "/weather" // 👇 changed code @attribute [Authorize(Policy = "VerifiedEmail")] // ☝️ changed code @attribute [StreamRendering] //...existing code...
MyBlazorSaaS.Client/Pages
フォルダ内の Counter.razor
ページと FetchData.razor
ページにも、この変更を適用します。
これで完了です。顧客オンボーディングフローにメールアドレス検証ステップが追加され、スパムや不正行為のリスクを軽減します。
Try out Auth0 authentication for free.
Get started →まとめ
Auth0は、ユーザーがアプリケーションにサインアップすると常にメールアドレス検証メッセージを送信します。しかし、どのシナリオでユーザーのメールアドレスが検証済みかどうかを確認するかは、開発者が決定します。
本記事で示した例では、Blazorで開発したB2B SaaSアプリケーションの顧客をオンボーディングするケースを取り上げ、新しいOrganizationの作成を許可する前にメールアドレス検証ステップを追加しました。
また、検証メールの再送信を許可し、ユーザーのメールアドレスがまだ検証されていない場合にアプリケーションの保護されたページへのアクセスを禁止する方法も説明しました。
本記事で作成したプロジェクトの最終的なコードは、GitHubリポジトリで確認できます。
About the author
Andrea Chiarelli
Principal Developer Advocate
I have over 20 years of experience as a software engineer and technical author. Throughout my career, I've used several programming languages and technologies for the projects I was involved in, ranging from C# to JavaScript, ASP.NET to Node.js, Angular to React, SOAP to REST APIs, etc.
In the last few years, I've been focusing on simplifying the developer experience with Identity and related topics, especially in the .NET ecosystem.