ログイン、電子メール確認、パスワード リセットを使用して安全な ASP.NET MVC 5 Web アプリを作成する (C#)
作成者 : Rick Anderson
このチュートリアルでは、ASP.NET ID メンバーシップ システムを使用して、電子メールの確認とパスワードリセットを使用して ASP.NET MVC 5 Web アプリを構築する方法について説明します。
.NET Core を使用するこのチュートリアルの更新バージョンについては、「ASP.NET Coreでのアカウントの確認とパスワードの回復」を参照してください。
ASP.NET MVC アプリを作成する
まず、Visual Studio Express 2013 for Web または Visual Studio 2013をインストールして実行します。 Update 3 以降Visual Studio 2013インストールします。
Note
警告: このチュートリアルを完了するには、Update 3 以降Visual Studio 2013インストールする必要があります。
新しい ASP.NET Web プロジェクトを作成し、MVC テンプレートを選択します。 Web Formsでは ASP.NET ID もサポートされているため、Web フォーム アプリでも同様の手順を実行できます。
既定の認証は [個々のユーザー アカウント] のままにします。 Azure でアプリをホストする場合は、[チェック] チェック ボックスをオンのままにします。 チュートリアルの後半では、Azure にデプロイします。 Azure アカウントは無料で開くことができます。
SSL を 使用するようにプロジェクトを設定します。
アプリを実行し、[ 登録 ] リンクをクリックしてユーザーを登録します。 この時点で、電子メールの検証は [EmailAddress] 属性のみです。
[サーバー エクスプローラー] で、Data Connections\DefaultConnection\Tables\AspNetUsers に移動し、右クリックして [テーブル定義を開く] を選択します。
次の図は、スキーマを
AspNetUsers
示しています。AspNetUsers テーブルを右クリックし、[テーブル データの表示] を選択します。
この時点で、メールは確認されていません。行をクリックし、[削除] を選択します。 次の手順でこのメールをもう一度追加し、確認メールを送信します。
Email確認
新しいユーザー登録の電子メールを確認して、他のユーザーを偽装していないことを確認することをお勧めします (つまり、他のユーザーのメールに登録していません)。 ディスカッション フォーラムがあるとします。これは、 として"joe@contoso.com"
登録できないように"bob@example.com"
したいとします。 電子メールの確認がない場合は、 "joe@contoso.com"
アプリから不要なメールを受け取る可能性があります。 Bob が誤って として "bib@example.com"
登録され、それに気付かなかったとします。アプリに正しいメールがないため、パスワードの回復を使用できないとします。 Email確認では、ボットからの保護が制限され、決定されたスパマーからの保護は提供されません。登録に使用できる多くの動作する電子メール エイリアスがあります。
一般に、新しいユーザーが電子メール、SMS テキスト メッセージ、または別のメカニズムによって確認される前に、Web サイトにデータを投稿できないようにする必要があります。 以下のセクションでは、電子メールの確認を有効にし、新しく登録されたユーザーがメールが確認されるまでログインできないようにコードを変更します。
SendGrid をフックする
このセクションの手順は最新ではありません。 更新された手順については 、「SendGrid 電子メール プロバイダーの構成」 を参照してください。
このチュートリアルでは SendGrid を介してメール通知を追加する方法のみを示しますが、SMTP やその他のメカニズムを使用してメールを送信できます ( その他のリソースを参照してください)。
パッケージ マネージャー コンソールで、次のコマンドを入力します。
Install-Package SendGrid
Azure SendGrid サインアップ ページに移動し、無料の SendGrid アカウントに登録します。 sendGrid を構成するには、 App_Start/IdentityConfig.cs で次のようなコードを追加します。
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( "Joe@contoso.com", "Joe S."); myMessage.Subject = message.Subject; myMessage.Text = message.Body; myMessage.Html = message.Body; var credentials = new NetworkCredential( ConfigurationManager.AppSettings["mailAccount"], ConfigurationManager.AppSettings["mailPassword"] ); // 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 SendGrid;
using System.Net;
using System.Configuration;
using System.Diagnostics;
このサンプルをシンプルにするために、アプリ設定を web.config ファイルに格納します。
</connectionStrings>
<appSettings>
<add key="webpages:Version" value="3.0.0.0" />
<!-- Markup removed for clarity. -->
<add key="mailAccount" value="xyz" />
<add key="mailPassword" value="password" />
</appSettings>
<system.web>
警告
セキュリティ - ソース コードに機密データを保存しないでください。 アカウントと資格情報は appSetting に格納されます。 Azure では、これらの値をAzure portalの [構成] タブに安全に格納できます。 パスワードやその他の機密データを ASP.NET と Azure にデプロイするためのベスト プラクティスに関するページを参照してください。
アカウント コントローラーで電子メールの確認を有効にする
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
var callbackUrl = Url.Action("ConfirmEmail", "Account",
new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
await UserManager.SendEmailAsync(user.Id,
"Confirm your account", "Please confirm your account by clicking <a href=\""
+ callbackUrl + "\">here</a>");
return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
Views\Account\ConfirmEmail.cshtml ファイルに正しい razor 構文があることを確認します。 ( 最初の行の @ 文字が見つからない可能性があります。
@{
ViewBag.Title = "Confirm Email";
}
<h2>@ViewBag.Title.</h2>
<div>
<p>
Thank you for confirming your email. Please @Html.ActionLink("Click here to Log in", "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" })
</p>
</div>
アプリを実行し、[登録] リンクをクリックします。 登録フォームを送信すると、ログインします。
メール アカウントを確認し、リンクをクリックしてメールを確認します。
ログイン前に電子メールの確認を要求する
現在、ユーザーが登録フォームを完了すると、ユーザーはログインします。 通常、ログインする前にメールを確認する必要があります。 以下のセクションでは、新しいユーザーがログイン (認証) される前に確認済みのメールを受け取る必要があるコードを変更します。 次の HttpPost Register
強調表示された変更を使用して メソッドを更新します。
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
// Comment the following line to prevent log in until the user is confirmed.
// await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
var callbackUrl = Url.Action("ConfirmEmail", "Account",
new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
await UserManager.SendEmailAsync(user.Id, "Confirm your account",
"Please confirm your account by clicking <a href=\"" + callbackUrl + "\">here</a>");
// Uncomment to debug locally
// TempData["ViewBagLink"] = callbackUrl;
ViewBag.Message = "Check your email and confirm your account, you must be confirmed "
+ "before you can log in.";
return View("Info");
//return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
メソッドをコメントアウト SignInAsync
すると、ユーザーは登録によってサインインされません。 この TempData["ViewBagLink"] = callbackUrl;
行を使用すると、メールを送信せずに アプリをデバッグ し、登録をテストできます。 ViewBag.Message
は、確認命令を表示するために使用されます。 ダウンロード サンプルには、電子メールを設定せずに電子メールの確認をテストするコードが含まれており、アプリケーションのデバッグにも使用できます。
ファイルを Views\Shared\Info.cshtml
作成し、次の Razor マークアップを追加します。
@{
ViewBag.Title = "Info";
}
<h2>@ViewBag.Title.</h2>
<h3>@ViewBag.Message</h3>
Authorize 属性を Home コントローラーの Contact
action メソッドに追加します。 [ 連絡先 ] リンクをクリックすると、匿名ユーザーにアクセス権がなく、認証済みユーザーがアクセス権を持っていることを確認できます。
[Authorize]
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
アクション メソッドも更新する HttpPost Login
必要があります。
//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
// Require the user to have a confirmed email before they can log on.
var user = await UserManager.FindByNameAsync(model.Email);
if (user != null)
{
if (!await UserManager.IsEmailConfirmedAsync(user.Id))
{
ViewBag.errorMessage = "You must have a confirmed email to log on.";
return View("Error");
}
}
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, change to shouldLockout: true
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
Views\Shared\Error.cshtml ビューを更新して、エラー メッセージを表示します。
@model System.Web.Mvc.HandleErrorInfo
@{
ViewBag.Title = "Error";
}
<h1 class="text-danger">Error.</h1>
@{
if (String.IsNullOrEmpty(ViewBag.errorMessage))
{
<h2 class="text-danger">An error occurred while processing your request.</h2>
}
else
{
<h2 class="text-danger">@ViewBag.errorMessage</h2>
}
}
テスト対象の電子メール エイリアスを含む AspNetUsers テーブル内のすべてのアカウントを削除します。 アプリを実行し、メール アドレスを確認するまでログインできないことを確認します。 メール アドレスを確認したら、[ 連絡先 ] リンクをクリックします。
パスワードの回復/リセット
アカウント コントローラーのアクション メソッドから HttpPost ForgotPassword
コメント文字を削除します。
//
// POST: /Account/ForgotPassword
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ForgotPassword(ForgotPasswordViewModel model)
{
if (ModelState.IsValid)
{
var user = await UserManager.FindByNameAsync(model.Email);
if (user == null || !(await UserManager.IsEmailConfirmedAsync(user.Id)))
{
// Don't reveal that the user does not exist or is not confirmed
return View("ForgotPasswordConfirmation");
}
string code = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
await UserManager.SendEmailAsync(user.Id, "Reset Password", "Please reset your password by clicking <a href=\"" + callbackUrl + "\">here</a>");
return RedirectToAction("ForgotPasswordConfirmation", "Account");
}
// If we got this far, something failed, redisplay form
return View(model);
}
Views\Account\Login.cshtml razor ビュー ファイルの ActionLink からForgotPassword
コメント文字を削除します。
@using MvcPWy.Models
@model LoginViewModel
@{
ViewBag.Title = "Log in";
}
<h2>@ViewBag.Title.</h2>
<div class="row">
<div class="col-md-8">
<section id="loginForm">
@using (Html.BeginForm("Login", "Account", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
@Html.AntiForgeryToken()
<h4>Use a local account to log in.</h4>
<hr />
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
<div class="form-group">
@Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.Email, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.PasswordFor(m => m.Password, new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.Password, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<div class="checkbox">
@Html.CheckBoxFor(m => m.RememberMe)
@Html.LabelFor(m => m.RememberMe)
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Log in" class="btn btn-default" />
</div>
</div>
<p>
@Html.ActionLink("Register as a new user", "Register")
</p>
@* Enable this once you have account confirmation enabled for password reset functionality *@
<p>
@Html.ActionLink("Forgot your password?", "ForgotPassword")
</p>
}
</section>
</div>
<div class="col-md-4">
<section id="socialLoginForm">
@Html.Partial("_ExternalLoginsListPartial", new ExternalLoginListViewModel { ReturnUrl = ViewBag.ReturnUrl })
</section>
</div>
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
[ログイン] ページに、パスワードをリセットするためのリンクが表示されます。
メールの再送信の確認リンク
ユーザーが新しいローカル アカウントを作成すると、ログオンする前に使用する必要がある確認リンクが電子メールで送信されます。 ユーザーが誤って確認メールを削除した場合、または電子メールが届かなかった場合は、もう一度確認リンクを送信する必要があります。 次のコード変更は、これを有効にする方法を示しています。
Controllers\AccountController.cs ファイルの下部に次のヘルパー メソッドを追加します。
private async Task<string> SendEmailConfirmationTokenAsync(string userID, string subject)
{
string code = await UserManager.GenerateEmailConfirmationTokenAsync(userID);
var callbackUrl = Url.Action("ConfirmEmail", "Account",
new { userId = userID, code = code }, protocol: Request.Url.Scheme);
await UserManager.SendEmailAsync(userID, subject,
"Please confirm your account by clicking <a href=\"" + callbackUrl + "\">here</a>");
return callbackUrl;
}
新しいヘルパーを使用するように Register メソッドを更新します。
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
// Comment the following line to prevent log in until the user is confirmed.
// await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
string callbackUrl = await SendEmailConfirmationTokenAsync(user.Id, "Confirm your account");
ViewBag.Message = "Check your email and confirm your account, you must be confirmed "
+ "before you can log in.";
return View("Info");
//return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
ユーザー アカウントが確認されていない場合は、Login メソッドを更新してパスワードを再送信します。
//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
// Require the user to have a confirmed email before they can log on.
// var user = await UserManager.FindByNameAsync(model.Email);
var user = UserManager.Find(model.Email, model.Password);
if (user != null)
{
if (!await UserManager.IsEmailConfirmedAsync(user.Id))
{
string callbackUrl = await SendEmailConfirmationTokenAsync(user.Id, "Confirm your account-Resend");
// Uncomment to debug locally
// ViewBag.Link = callbackUrl;
ViewBag.errorMessage = "You must have a confirmed email to log on. "
+ "The confirmation token has been resent to your email account.";
return View("Error");
}
}
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, change to shouldLockout: true
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
ソーシャルとローカルのログイン アカウントを結合する
メールのリンクをクリックして、ローカル アカウントとソーシャル アカウントを組み合わせることができます。 次のシーケンス RickAndMSFT@gmail.com では、最初にローカル ログインとして作成されますが、最初にソーシャル ログとしてアカウントを作成してから、ローカル ログインを追加できます。
[管理] リンクをクリックします。 このアカウントに関連付けられている 外部ログイン: 0 に注意してください。
別のログイン サービスへのリンクをクリックし、アプリの要求を受け入れます。 2 つのアカウントが組み合わされているため、どちらのアカウントでもログオンできます。 認証サービスのソーシャル ログがダウンした場合や、ソーシャル アカウントへのアクセスが失われた可能性が高い場合に備えて、ユーザーにローカル アカウントを追加することが必要な場合があります。
次の図では、Tom はソーシャル ログインです (ページに表示されている 外部ログイン: 1 から確認できます)。
[パスワードの 選択 ] をクリックすると、同じアカウントに関連付けられているローカル ログオンを追加できます。
Email確認の詳細
ASP.NET ID を使用したアカウントの確認とパスワードの回復 に関するチュートリアルでは、このトピックで詳しく説明します。
アプリのデバッグ
リンクを含むメールが届かない場合:
- 迷惑メールフォルダーまたはスパム フォルダーを確認します。
- SendGrid アカウントにログインし、[Email アクティビティ] リンクをクリックします。
電子メールなしで検証リンクをテストするには、 完成したサンプルをダウンロードします。 確認リンクと確認コードがページに表示されます。
その他のリソース
- ASP.NET ID の推奨リソースへのリンク
- ASP.NET ID を使用したアカウントの確認とパスワードの回復 パスワードの回復とアカウントの確認について詳しく説明します。
- Facebook、Twitter、LinkedIn、Google OAuth2 のサインオンを使用した MVC 5 アプリ このチュートリアルでは、Facebook と Google OAuth 2 承認を使用して ASP.NET MVC 5 アプリを作成する方法について説明します。 また、IDENTITY データベースにデータを追加する方法も示します。
- メンバーシップ、OAuth、SQL Databaseを使用して Secure ASP.NET MVC アプリを Azure にデプロイします。 このチュートリアルでは、Azure デプロイ、ロールを使用してアプリをセキュリティで保護する方法、メンバーシップ API を使用してユーザーとロールを追加する方法、追加のセキュリティ機能を追加します。
- OAuth 2 用の Google アプリを作成し、そのアプリをプロジェクトに接続する
- Facebook でアプリを作成し、アプリをプロジェクトに接続する
- プロジェクトでの SSL の設定
フィードバック
https://aka.ms/ContentUserFeedback」を参照してください。
以下は間もなく提供いたします。2024 年を通じて、コンテンツのフィードバック メカニズムとして GitHub の issue を段階的に廃止し、新しいフィードバック システムに置き換えます。 詳細については、「フィードバックの送信と表示