ユーザー登録、電子メール確認、パスワード リセットを利用し、安全な ASP.NET Web フォームを作成する (C#)
作成者: Erik Reitan
このチュートリアルでは、ASP.NET Identity メンバーシップ システムを使って、ユーザーの登録、メール アドレスの確認、パスワードのリセットを備えた ASP.NET Web Forms アプリを構築する方法について説明します。 このチュートリアルは、Rick Anderson の MVC チュートリアルが基になっています。
はじめに
このチュートリアルでは、Visual Studio と ASP.NET 4.5 を使って ASP.NET Web Forms アプリケーションを作成し、ユーザーの登録、メール アドレスの確認、パスワードのリセットを含むセキュリティ保護された Web Forms アプリを作成するために必要な手順について説明します。
チュートリアルのタスクと情報:
- ASP.NET Web Forms アプリを作成する
- SendGrid をフックする
- ログイン前にメール アドレスの確認を要求する
- パスワードの回復とリセット
- メール アドレス確認リンクを再送信する
- アプリのトラブルシューティング
- その他のリソース
ASP.NET Web Forms アプリを作成する
Note
警告: このチュートリアルを完了するには、Visual Studio 2013 Update 3 以降をインストールする必要があります。
新しいプロジェクトを作成し ([ファイル] ->[新しいプロジェクト])、[新しいプロジェクト] ダイアログ ボックスで [ASP.NET Web アプリケーション] テンプレートと最新の .NET Framework バージョンを選びます。
[新しい ASP.NET プロジェクト] ダイアログ ボックスで、[Web Forms] テンプレートを選びます。 既定の認証は個々のユーザー アカウントのままにします。 Azure でアプリをホストする場合は、[クラウドにホストする] チェック ボックスをオンのままにします。
次に、[OK] をクリックして、新しいプロジェクトを作成します。
プロジェクトで Secure Sockets Layer (SSL) を有効にします。 Web Forms の概要チュートリアル シリーズの「プロジェクトに対して SSL を有効にする」セクションの手順のようにします。
アプリを実行し、[登録] リンクをクリックして新しいユーザーを登録します。 この時点で、メール アドレスに対して行われる検証は、[EmailAddress] 属性に基づく、メール アドレスが正しい形式であることの確認だけです。 コードを変更して、メール アドレスの確認を追加します。 ブラウザー ウィンドウを閉じます。
Visual Studio のサーバー エクスプローラーで ([表示] ->[サーバー エクスプローラー])、Data Connections\DefaultConnection\Tables\AspNetUsers に移動し、右クリックして [テーブル定義を開く] を選びます。
次に示すのは、
AspNetUsers
テーブルのスキーマの図です。サーバー エクスプローラーで AspNetUsers テーブルを右クリックして、[テーブル データの表示] を選びます。
この時点で、登録ユーザーのメール アドレスは確認されていません。行をクリックし、[削除] を選んでユーザーを削除します。 次のステップでこのメール アドレスを再び追加し、メール アドレスに確認メッセージを送信します。
メール アドレスの確認
新しいユーザーの登録の間にメール アドレスを確認して、他のユーザーを偽装していない (つまり、他のユーザーのメール アドレスで登録されていない) ことを検証するのがベスト プラクティスです。 ディスカッション フォーラムを管理していて、"bob@cpandl.com"
が "joe@contoso.com"
として登録できないようにしたいとします。 メール アドレスを確認しないと、"joe@contoso.com"
はアプリから不要なメールを受け取る可能性があります。 Bob が誤って "bib@cpandl.com"
として登録して、これに気付かなかったとします。アプリに正しいメール アドレスがないため、パスワード回復を使用できません。 メール アドレスの確認では、ボットからの保護が制限され、本気のスパム送信者から保護することはできません。
一般に、新しいユーザーがメール、SMS テキスト メッセージ、または別のメカニズムによって確認される前に、Web サイトにデータを投稿できないようにする必要があります。 以下のセクションでは、メール アドレスの確認を有効にし、メール アドレスが確認されるまで新しく登録されたユーザーがログインできないようにコードを変更します。 このチュートリアルでは、メール サービス SendGrid を使います。
SendGrid をフックする
このチュートリアルを書いてから、SendGrid の API が変更されています。 現在の SendGrid の説明については、SendGrid またはアカウントの確認とパスワードの回復の有効化に関する記事をご覧ください。
このチュートリアルでは SendGrid を使ってメール通知を追加する方法のみを示しますが、SMTP や他のメカニズムを使ってメールを送信することもできます (「その他のリソース」をご覧ください)。
Visual Studio でパッケージ マネージャー コンソールを開き ([ツール] ->[NuGet パッケージ マネージャー] ->[パッケージ マネージャー コンソール])、次のコマンドを入力します。
Install-Package SendGrid
Azure SendGrid のサインアップ ページに移動し、無料の SendGrid アカウントに登録します。 SendGrid のサイトで無料の SendGrid アカウントに直接サインアップすることもできます。
ソリューション エクスプローラーで、App_Start フォルダーの IdentityConfig.cs ファイルを開き、次の黄色で強調されているコードを
EmailService
クラスに追加して、SendGrid を構成します。public class EmailService : IIdentityMessageService { public async Task SendAsync(IdentityMessage message) { await configSendGridasync(message); } // Use NuGet to install SendGrid (Basic C# client lib) private async Task configSendGridasync(IdentityMessage message) { var myMessage = new SendGridMessage(); myMessage.AddTo(message.Destination); myMessage.From = new System.Net.Mail.MailAddress( "Royce@contoso.com", "Royce Sellars (Contoso Admin)"); myMessage.Subject = message.Subject; myMessage.Text = message.Body; myMessage.Html = message.Body; var credentials = new NetworkCredential( ConfigurationManager.AppSettings["emailServiceUserName"], ConfigurationManager.AppSettings["emailServicePassword"] ); // Create a Web transport for sending email. var transportWeb = new Web(credentials); // Send the email. if (transportWeb != null) { await transportWeb.DeliverAsync(myMessage); } else { Trace.TraceError("Failed to create Web transport."); await Task.FromResult(0); } } }
また、次の
using
ステートメントを IdentityConfig.cs ファイルの先頭に追加します。using SendGrid; using System.Net; using System.Configuration; using System.Diagnostics;
このサンプルを複雑にしないため、メール サービス アカウントの値は、web.config ファイルの
appSettings
セクションに格納します。 次の黄色で強調されている XML を、プロジェクトのルートにある web.config ファイルに追加します。</connectionStrings> <appSettings> <add key="emailServiceUserName" value="[EmailServiceAccountUserName]" /> <add key="emailServicePassword" value="[EmailServiceAccountPassword]" /> </appSettings> <system.web>
警告
セキュリティ - 機密データをソース コード内に保存しないでください。 この例では、アカウントと資格情報は、Web.config ファイルの appSetting セクションに格納されます。 Azure では、これらの値を Azure portal の [構成] タブに安全に保存できます。 関連情報については、Rick Anderson の ASP.NET と Azure へのパスワードや他の機密データの配置のベスト プラクティスに関するトピックを参照してください。
SendGrid の認証に使う値 (ユーザー名とパスワード) を反映するようにメール サービスの値を追加して、アプリからメールを正常に送信できるようにします。 SendGrid に指定したメール アドレスではなく、必ず SendGrid アカウントの名前を使ってください。
メール アドレスの確認を有効にする
メール アドレスの確認を有効にするには、次の手順のようにして登録コードを変更します。
Account フォルダーの Register.aspx.cs コードビハインドを開き、
CreateUser_Click
メソッドを更新して、次の強調されている変更を有効にします。protected void CreateUser_Click(object sender, EventArgs e) { var manager = Context.GetOwinContext().GetUserManager<ApplicationUserManager>(); var user = new ApplicationUser() { UserName = Email.Text, Email = Email.Text }; IdentityResult result = manager.Create(user, Password.Text); if (result.Succeeded) { // For more information on how to enable account confirmation and password reset please visit https://go.microsoft.com/fwlink/?LinkID=320771 string code = manager.GenerateEmailConfirmationToken(user.Id); string callbackUrl = IdentityHelper.GetUserConfirmationRedirectUrl(code, user.Id, Request); manager.SendEmail(user.Id, "Confirm your account", "Please confirm your account by clicking <a href=\"" + callbackUrl + "\">here</a>."); IdentityHelper.SignIn(manager, user, isPersistent: false); IdentityHelper.RedirectToReturnUrl(Request.QueryString["ReturnUrl"], Response); } else { ErrorMessage.Text = result.Errors.FirstOrDefault(); } }
ソリューション エクスプローラーで Default.aspx を右クリックして、[スタート ページに設定] を選びます。
F5 キーを押してアプリを実行します。ページが表示されたら、[登録] リンクをクリックして [登録] ページを表示します。
メール アドレスとパスワードを入力し、[登録] ボタンをクリックして、SendGrid 経由でメール メッセージを送信します。
プロジェクトとコードの現在の状態では、ユーザーは、アカウントを確認していなくても、登録フォームを完了するとログインできるようになります。メール アカウントを確認し、リンクをクリックしてメール アドレスを確認します。
登録フォームを送信したら、ログインが行われます。
ログインの前にメール アドレスの確認を要求する
メール アカウントは確認しましたが、この時点では、完全にサインインするために確認メールに含まれるリンクをクリックする必要はありません。 次のセクションでは、新しいユーザーがログイン (認証) される前に確認済みのメール アドレスを持つことを要求するようにコードを変更します。
Visual Studio のソリューション エクスプローラーで、Accounts フォルダーに含まれる Register.aspx.cs コードビハインド内の
CreateUser_Click
イベントを、次の強調された変更で更新します。protected void CreateUser_Click(object sender, EventArgs e) { var manager = Context.GetOwinContext().GetUserManager<ApplicationUserManager>(); var user = new ApplicationUser() { UserName = Email.Text, Email = Email.Text }; IdentityResult result = manager.Create(user, Password.Text); if (result.Succeeded) { // For more information on how to enable account confirmation and password reset please visit https://go.microsoft.com/fwlink/?LinkID=320771 string code = manager.GenerateEmailConfirmationToken(user.Id); string callbackUrl = IdentityHelper.GetUserConfirmationRedirectUrl(code, user.Id, Request); manager.SendEmail(user.Id, "Confirm your account", "Please confirm your account by clicking <a href=\"" + callbackUrl + "\">here</a>."); if (user.EmailConfirmed) { IdentityHelper.SignIn(manager, user, isPersistent: false); IdentityHelper.RedirectToReturnUrl(Request.QueryString["ReturnUrl"], Response); } else { ErrorMessage.Text = "An email has been sent to your account. Please view the email and confirm your account to complete the registration process."; } } else { ErrorMessage.Text = result.Errors.FirstOrDefault(); } }
Login.aspx.cs コードビハインドの
LogIn
メソッドを、次の強調された変更で更新します。protected void LogIn(object sender, EventArgs e) { if (IsValid) { // Validate the user password var manager = Context.GetOwinContext().GetUserManager<ApplicationUserManager>(); var signinManager = Context.GetOwinContext().GetUserManager<ApplicationSignInManager>(); // Require the user to have a confirmed email before they can log on. var user = manager.FindByName(Email.Text); if (user != null) { if (!user.EmailConfirmed) { FailureText.Text = "Invalid login attempt. You must have a confirmed email account."; ErrorMessage.Visible = true; } else { // This doen't count login failures towards account lockout // To enable password failures to trigger lockout, change to shouldLockout: true var result = signinManager.PasswordSignIn(Email.Text, Password.Text, RememberMe.Checked, shouldLockout: false); switch (result) { case SignInStatus.Success: IdentityHelper.RedirectToReturnUrl(Request.QueryString["ReturnUrl"], Response); break; case SignInStatus.LockedOut: Response.Redirect("/Account/Lockout"); break; case SignInStatus.RequiresVerification: Response.Redirect(String.Format("/Account/TwoFactorAuthenticationSignIn?ReturnUrl={0}&RememberMe={1}", Request.QueryString["ReturnUrl"], RememberMe.Checked), true); break; case SignInStatus.Failure: default: FailureText.Text = "Invalid login attempt"; ErrorMessage.Visible = true; break; } } } } }
アプリケーションの実行
ユーザーのメール アドレスが確認されているかどうかを調べるコードを実装したので、Register ページと Login ページの両方で機能を確認できます。
- AspNetUsers テーブルで、テスト対象のメール エイリアスを含むすべてのアカウントを削除します。
- アプリを実行し (F5 キー)、メール アドレスを確認するまでユーザーとして登録できないことを検証します。
- 送信したばかりのメールで新しいアカウントを確認する前に、新しいアカウントでログインを試みます。
ログインできず、確認済みのメール アカウントが必要であることがわかります。 - メール アドレスを確認したら、アプリにログインします。
パスワードの回復とリセット
Visual Studio で、Account フォルダーに含まれる Forgot.aspx.cs コードビハインド内の
Forgot
メソッドからコメント文字を削除して、メソッドが次のような表記になるようにします。protected void Forgot(object sender, EventArgs e) { if (IsValid) { // Validate the user's email address var manager = Context.GetOwinContext().GetUserManager<ApplicationUserManager>(); ApplicationUser user = manager.FindByName(Email.Text); if (user == null || !manager.IsEmailConfirmed(user.Id)) { FailureText.Text = "The user either does not exist or is not confirmed."; ErrorMessage.Visible = true; return; } // For more information on how to enable account confirmation and password reset please visit https://go.microsoft.com/fwlink/?LinkID=320771 // Send email with the code and the redirect to reset password page string code = manager.GeneratePasswordResetToken(user.Id); string callbackUrl = IdentityHelper.GetResetPasswordRedirectUrl(code, Request); manager.SendEmail(user.Id, "Reset Password", "Please reset your password by clicking <a href=\"" + callbackUrl + "\">here</a>."); loginForm.Visible = false; DisplayEmail.Visible = true; } }
Login.aspx ページを開きます。 loginForm セクションの最後近くにあるマークアップを、次の強調された部分に置き換えます。
<%@ Page Title="Log in" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Login.aspx.cs" Inherits="WebForms002.Account.Login" Async="true" %> <%@ Register Src="~/Account/OpenAuthProviders.ascx" TagPrefix="uc" TagName="OpenAuthProviders" %> <asp:Content runat="server" ID="BodyContent" ContentPlaceHolderID="MainContent"> <h2><%: Title %>.</h2> <div class="row"> <div class="col-md-8"> <section id="loginForm"> <div class="form-horizontal"> <h4>Use a local account to log in.</h4> <hr /> <asp:PlaceHolder runat="server" ID="ErrorMessage" Visible="false"> <p class="text-danger"> <asp:Literal runat="server" ID="FailureText" /> </p> </asp:PlaceHolder> <div class="form-group"> <asp:Label runat="server" AssociatedControlID="Email" CssClass="col-md-2 control-label">Email</asp:Label> <div class="col-md-10"> <asp:TextBox runat="server" ID="Email" CssClass="form-control" TextMode="Email" /> <asp:RequiredFieldValidator runat="server" ControlToValidate="Email" CssClass="text-danger" ErrorMessage="The email field is required." /> </div> </div> <div class="form-group"> <asp:Label runat="server" AssociatedControlID="Password" CssClass="col-md-2 control-label">Password</asp:Label> <div class="col-md-10"> <asp:TextBox runat="server" ID="Password" TextMode="Password" CssClass="form-control" /> <asp:RequiredFieldValidator runat="server" ControlToValidate="Password" CssClass="text-danger" ErrorMessage="The password field is required." /> </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <div class="checkbox"> <asp:CheckBox runat="server" ID="RememberMe" /> <asp:Label runat="server" AssociatedControlID="RememberMe">Remember me?</asp:Label> </div> </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <asp:Button runat="server" OnClick="LogIn" Text="Log in" CssClass="btn btn-default" /> </div> </div> </div> <p> <asp:HyperLink runat="server" ID="RegisterHyperLink" ViewStateMode="Disabled">Register as a new user</asp:HyperLink> </p> <p> <%-- Enable this once you have account confirmation enabled for password reset functionality --%> <asp:HyperLink runat="server" ID="ForgotPasswordHyperLink" ViewStateMode="Disabled">Forgot your password?</asp:HyperLink> </p> </section> </div> <div class="col-md-4"> <section id="socialLoginForm"> <uc:OpenAuthProviders runat="server" ID="OpenAuthLogin" /> </section> </div> </div> </asp:Content>
Login.aspx.cs コードビハインドを開き、
Page_Load
イベント ハンドラーから次の黄色で強調されているコード行のコメントを解除します。protected void Page_Load(object sender, EventArgs e) { RegisterHyperLink.NavigateUrl = "Register"; // Enable this once you have account confirmation enabled for password reset functionality ForgotPasswordHyperLink.NavigateUrl = "Forgot"; OpenAuthLogin.ReturnUrl = Request.QueryString["ReturnUrl"]; var returnUrl = HttpUtility.UrlEncode(Request.QueryString["ReturnUrl"]); if (!String.IsNullOrEmpty(returnUrl)) { RegisterHyperLink.NavigateUrl += "?ReturnUrl=" + returnUrl; } }
F5 キーを押してアプリを実行します。ページが表示されたら、[Log in] リンクをクリックします。
[Forgot your password?] リンクをクリックして、[Forgot Password] ページを表示します。
メール アドレスを入力し、[Submit] ボタンをクリックして、パスワードをリセットできるアドレスにメールを送信します。
メール アカウントを確認し、リンクをクリックして [Reset Password] ページを表示します。[Reset Password] ページで、メール アドレス、パスワード、確認パスワードを入力します。 次に、[Reset] ボタンを選びます。
パスワードが正常にリセットされると、[Password Changed] ページが表示されます。 新しいパスワードでログインできるようになります。
メール アドレス確認リンクを再送信する
ユーザーが新しいローカル アカウントを作成すると、ログオンする前に使用する必要がある確認リンクがメールで送信されます。 ユーザーが誤って確認メールを削除した場合、またはメールが届かなかった場合は、確認リンクが再送信される必要があります。 次のコード変更は、これを有効にする方法を示しています。
Visual Studio で Login.aspx.cs コードビハインドを開き、
LogIn
イベント ハンドラーの後に次のイベント ハンドラーを追加します。protected void SendEmailConfirmationToken(object sender, EventArgs e) { var manager = Context.GetOwinContext().GetUserManager<ApplicationUserManager>(); var user = manager.FindByName(Email.Text); if (user != null) { if (!user.EmailConfirmed) { string code = manager.GenerateEmailConfirmationToken(user.Id); string callbackUrl = IdentityHelper.GetUserConfirmationRedirectUrl(code, user.Id, Request); manager.SendEmail(user.Id, "Confirm your account", "Please confirm your account by clicking <a href=\"" + callbackUrl + "\">here</a>."); FailureText.Text = "Confirmation email sent. Please view the email and confirm your account."; ErrorMessage.Visible = true; ResendConfirm.Visible = false; } } }
Login.aspx.cs コードビハインドの
LogIn
イベント ハンドラーを、次の黄色で強調されているコードのように変更します。protected void LogIn(object sender, EventArgs e) { if (IsValid) { // Validate the user password var manager = Context.GetOwinContext().GetUserManager<ApplicationUserManager>(); var signinManager = Context.GetOwinContext().GetUserManager<ApplicationSignInManager>(); // Require the user to have a confirmed email before they can log on. var user = manager.FindByName(Email.Text); if (user != null) { if (!user.EmailConfirmed) { FailureText.Text = "Invalid login attempt. You must have a confirmed email address. Enter your email and password, then press 'Resend Confirmation'."; ErrorMessage.Visible = true; ResendConfirm.Visible = true; } else { // This doen't count login failures towards account lockout // To enable password failures to trigger lockout, change to shouldLockout: true var result = signinManager.PasswordSignIn(Email.Text, Password.Text, RememberMe.Checked, shouldLockout: false); switch (result) { case SignInStatus.Success: IdentityHelper.RedirectToReturnUrl(Request.QueryString["ReturnUrl"], Response); break; case SignInStatus.LockedOut: Response.Redirect("/Account/Lockout"); break; case SignInStatus.RequiresVerification: Response.Redirect(String.Format("/Account/TwoFactorAuthenticationSignIn?ReturnUrl={0}&RememberMe={1}", Request.QueryString["ReturnUrl"], RememberMe.Checked), true); break; case SignInStatus.Failure: default: FailureText.Text = "Invalid login attempt"; ErrorMessage.Visible = true; break; } } } } }
次の黄色で強調されているコードを追加して、Login.aspx ページを更新します。
<%@ Page Title="Log in" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Login.aspx.cs" Inherits="WebForms002.Account.Login" Async="true" %> <%@ Register Src="~/Account/OpenAuthProviders.ascx" TagPrefix="uc" TagName="OpenAuthProviders" %> <asp:Content runat="server" ID="BodyContent" ContentPlaceHolderID="MainContent"> <h2><%: Title %>.</h2> <div class="row"> <div class="col-md-8"> <section id="loginForm"> <div class="form-horizontal"> <h4>Use a local account to log in.</h4> <hr /> <asp:PlaceHolder runat="server" ID="ErrorMessage" Visible="false"> <p class="text-danger"> <asp:Literal runat="server" ID="FailureText" /> </p> </asp:PlaceHolder> <div class="form-group"> <asp:Label runat="server" AssociatedControlID="Email" CssClass="col-md-2 control-label">Email</asp:Label> <div class="col-md-10"> <asp:TextBox runat="server" ID="Email" CssClass="form-control" TextMode="Email" /> <asp:RequiredFieldValidator runat="server" ControlToValidate="Email" CssClass="text-danger" ErrorMessage="The email field is required." /> </div> </div> <div class="form-group"> <asp:Label runat="server" AssociatedControlID="Password" CssClass="col-md-2 control-label">Password</asp:Label> <div class="col-md-10"> <asp:TextBox runat="server" ID="Password" TextMode="Password" CssClass="form-control" /> <asp:RequiredFieldValidator runat="server" ControlToValidate="Password" CssClass="text-danger" ErrorMessage="The password field is required." /> </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <div class="checkbox"> <asp:CheckBox runat="server" ID="RememberMe" /> <asp:Label runat="server" AssociatedControlID="RememberMe">Remember me?</asp:Label> </div> </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <asp:Button runat="server" OnClick="LogIn" Text="Log in" CssClass="btn btn-default" /> <asp:Button runat="server" ID="ResendConfirm" OnClick="SendEmailConfirmationToken" Text="Resend confirmation" Visible="false" CssClass="btn btn-default" /> </div> </div> </div> <p> <asp:HyperLink runat="server" ID="RegisterHyperLink" ViewStateMode="Disabled">Register as a new user</asp:HyperLink> </p> <p> <%-- Enable this once you have account confirmation enabled for password reset functionality --%> <asp:HyperLink runat="server" ID="ForgotPasswordHyperLink" ViewStateMode="Disabled">Forgot your password?</asp:HyperLink> </p> </section> </div> <div class="col-md-4"> <section id="socialLoginForm"> <uc:OpenAuthProviders runat="server" ID="OpenAuthLogin" /> </section> </div> </div> </asp:Content>
AspNetUsers テーブルで、テスト対象のメール エイリアスを含むすべてのアカウントを削除します。
アプリを実行して (F5 キー)、メール アドレスを登録します。
送信したばかりのメールで新しいアカウントを確認する前に、新しいアカウントでログインを試みます。
ログインできず、確認済みのメール アカウントが必要であることがわかります。 さらに、確認メッセージをメール アカウントに再送信できるようになりました。メール アドレスとパスワードを入力して、[Resend confirmation] ボタンを選びます。
新しく送信されたメール メッセージに基づいてメール アドレスを確認したら、アプリにログインします。
アプリのトラブルシューティング
資格情報を確認するためのリンクを含むメールが届かない場合:
- 迷惑メールまたはスパム フォルダーを確認します。
- SendGrid のアカウントにログインして、[Email Activity] リンクを選びます。
- Web.config の値として、SendGrid アカウントのメール アドレスではなく、SendGrid ユーザー アカウント名を使ったことを確認します。