ASP.NET ID を使用したアカウントの確認とパスワードの回復 (C#)
このチュートリアルを実行する前に、まず 、ログイン、電子メールの確認、パスワードリセットを使用して、セキュリティで保護された ASP.NET MVC 5 Web アプリを作成する必要があります。 このチュートリアルには詳細が含まれており、ローカル アカウントの確認用にメールを設定し、ユーザーが忘れたパスワードを ASP.NET ID でリセットできるようにする方法について説明します。
ローカル ユーザー アカウントでは、ユーザーがアカウントのパスワードを作成する必要があり、そのパスワードは (安全に) Web アプリに格納されます。 ASP.NET ID ではソーシャル アカウントもサポートされています。これは、ユーザーがアプリのパスワードを作成する必要はありません。 ソーシャル アカウントでは、 第三者 (Google、Twitter、Facebook、Microsoft など) を使用してユーザーを認証します。 このトピックでは、以下の内容を説明します。
- ASP.NET MVC アプリを作成 し、ASP.NET ID 機能を調べる。
- ID サンプルをビルドする
- メールの確認を設定する
新しいユーザーは、ローカル アカウントを作成するメール エイリアスを登録します。
[登録] ボタンを選択すると、検証トークンを含む確認メールがメール アドレスに送信されます。
ユーザーは、自分のアカウントの確認トークンを含む電子メールを送信されます。
リンクを選択すると、アカウントが確認されます。
パスワードの回復/リセット
パスワードを忘れたローカル ユーザーは、メール アカウントにセキュリティ トークンを送信して、パスワードをリセットできます。
ユーザーは間もなく、パスワードのリセットを許可するリンクが記載されたメールを受け取ります。
リンクを選択すると、[リセット] ページに移動します。
[ リセット ] ボタンを選択すると、パスワードがリセットされたことを確認します。
ASP.NET Web アプリを作成する
まず、 Visual Studio 2017 をインストールして実行します。
新しい ASP.NET Web プロジェクトを作成し、MVC テンプレートを選択します。 Web Forms ASP.NET ID もサポートされるため、Web フォーム アプリでも同様の手順を実行できます。
認証を [個々のユーザー アカウント] に変更します。
アプリを実行し、[ 登録 ] リンクを選択してユーザーを登録します。 この時点で、電子メールの検証は [EmailAddress] 属性のみです。
[サーバー エクスプローラー] で、[データ接続]\DefaultConnection\Tables\AspNetUsers に移動し、右クリックして [テーブル定義を開く] を選択します。
次の図は、スキーマを
AspNetUsers
示しています。AspNetUsers テーブルを右クリックし、[テーブル データの表示] を選択します。
この時点で、電子メールは確認されていません。
ASP.NET Identity の既定のデータ ストアは Entity Framework ですが、他のデータ ストアを使用し、フィールドを追加するように構成できます。 このチュートリアルの最後にある「 その他のリソース 」セクションを参照してください。
OWIN スタートアップ クラス ( Startup.cs ) は、アプリの起動時に呼び出され、App_Start\Startup.Auth.cs で メソッドを呼び出ConfigureAuth
します。これにより、OWIN パイプラインが構成され、id ASP.NET 初期化されます。 ConfigureAuth
メソッドを調べます。 各 CreatePerOwinContext
呼び出しでは、指定した型のインスタンスを OwinContext
作成するために要求ごとに 1 回呼び出されるコールバック (に保存) が登録されます。 各型 (ApplicationDbContext, ApplicationUserManager
) のコンストラクターとCreate
メソッドにブレークポイントを設定し、各要求で呼び出されることを確認できます。 と ApplicationUserManager
のApplicationDbContext
インスタンスは OWIN コンテキストに格納され、アプリケーション全体でアクセスできます。 ASP.NET Id は、Cookie ミドルウェアを介して OWIN パイプラインにフックされます。 詳細については、「 ASP.NET ID の UserManager クラスの要求ごとの有効期間管理」を参照してください。
セキュリティ プロファイルを変更すると、新しいセキュリティ スタンプが生成され、AspNetUsers テーブルのフィールドに格納されますSecurityStamp
。 フィールドは SecurityStamp
セキュリティ Cookie とは異なります。 セキュリティ Cookie はテーブル (または Identity DB 内の他の場所) に格納 AspNetUsers
されません。 セキュリティ Cookie トークンは DPAPI を使用して自己署名され、 と の有効期限情報を使用して UserId, SecurityStamp
作成されます。
Cookie ミドルウェアは、各要求で Cookie をチェックします。 クラスの メソッドが SecurityStampValidator
DB にヒットし、 で指定されているように、セキュリティ スタンプが定期的にチェックされますvalidateInterval
。Startup
これは、セキュリティ プロファイルを変更しない限り、(サンプルでは) 30 分ごとに発生します。 データベースへの移動を最小限に抑えるために、30 分の間隔が選択されました。 詳細については、 2 要素認証のチュートリアル を参照してください。
コード内のコメントに従って、 メソッドは UseCookieAuthentication
Cookie 認証をサポートします。 フィールドと関連付けられたコードは SecurityStamp
、アプリにセキュリティの追加レイヤーを提供します。パスワードを変更すると、ログインしたブラウザーからログアウトされます。 SecurityStampValidator.OnValidateIdentity
メソッドを使用すると、ユーザーがログインするときにセキュリティ トークンを検証できます。これは、パスワードを変更するとき、または外部ログインを使用するときに使用されます。 これは、古いパスワードで生成されたトークン (Cookie) が無効になるようにするために必要です。 サンプル プロジェクトでは、ユーザーのパスワードを変更すると、ユーザーに対して新しいトークンが生成され、以前のトークンはすべて無効になり、 SecurityStamp
フィールドが更新されます。
ID システムを使用すると、ユーザーのセキュリティ プロファイルが変更されたときに (たとえば、ユーザーがパスワードを変更したり、関連するログイン (Facebook、Google、Microsoft アカウントなど) を変更したりしたときに)、ユーザーがすべてのブラウザー インスタンスからログアウトされるようにアプリを構成できます。 たとえば、次の図は シングル サインアウト サンプル アプリを示しています。これにより、ユーザーは 1 つのボタンを選択してすべてのブラウザー インスタンス (この場合は IE、Firefox、Chrome) からサインアウトできます。 または、このサンプルでは、特定のブラウザー インスタンスからのみログアウトできます。
シングル サインアウト サンプル アプリでは、ID を ASP.NET してセキュリティ トークンを再生成する方法を示します。 これは、古いパスワードで生成されたトークン (Cookie) が無効になるようにするために必要です。 この機能により、アプリケーションに追加のセキュリティ層が提供されます。パスワードを変更すると、このアプリケーションにログインした場所にログアウトされます。
App_Start\IdentityConfig.cs ファイルには、 クラスと SmsService
クラスがApplicationUserManager
EmailService
含まれています。 EmailService
クラスと SmsService
クラスはそれぞれ インターフェイスをIIdentityMessageService
実装するため、各クラスに電子メールと SMS を構成するための一般的なメソッドがあります。 このチュートリアルでは SendGrid を介してメール通知を追加する方法のみを示していますが、SMTP やその他のメカニズムを使用して電子メールを送信できます。
Startup
クラスには、ソーシャル ログイン (Facebook、Twitter など) を追加するためのボイラー プレートも含まれています。詳細については、Facebook、Twitter、LinkedIn、Google OAuth2 のサインオンを使用した MVC 5 アプリのチュートリアルを参照してください。
ユーザー ID 情報を ApplicationUserManager
含む クラスを調べ、次の機能を構成します。
- パスワード強度の要件。
- ユーザー のロックアウト (試行と時間)。
- 2 要素認証 (2FA)。 私は別のチュートリアルで2FAとSMSをカバーします。
- 電子メールサービスと SMS サービスをフックする。 (別のチュートリアルでは SMS について説明します)。
クラスは ApplicationUserManager
ジェネリック UserManager<ApplicationUser>
クラスから派生します。 ApplicationUser
は IdentityUser から派生します。 IdentityUser
はジェネリック IdentityUser
クラスから派生します。
// Default EntityFramework IUser implementation
public class IdentityUser<TKey, TLogin, TRole, TClaim> : IUser<TKey>
where TLogin : IdentityUserLogin<TKey>
where TRole : IdentityUserRole<TKey>
where TClaim : IdentityUserClaim<TKey>
{
public IdentityUser()
{
Claims = new List<TClaim>();
Roles = new List<TRole>();
Logins = new List<TLogin>();
}
/// User ID (Primary Key)
public virtual TKey Id { get; set; }
public virtual string Email { get; set; }
public virtual bool EmailConfirmed { get; set; }
public virtual string PasswordHash { get; set; }
/// A random value that should change whenever a users credentials have changed (password changed, login removed)
public virtual string SecurityStamp { get; set; }
public virtual string PhoneNumber { get; set; }
public virtual bool PhoneNumberConfirmed { get; set; }
public virtual bool TwoFactorEnabled { get; set; }
/// DateTime in UTC when lockout ends, any time in the past is considered not locked out.
public virtual DateTime? LockoutEndDateUtc { get; set; }
public virtual bool LockoutEnabled { get; set; }
/// Used to record failures for the purposes of lockout
public virtual int AccessFailedCount { get; set; }
/// Navigation property for user roles
public virtual ICollection<TRole> Roles { get; private set; }
/// Navigation property for user claims
public virtual ICollection<TClaim> Claims { get; private set; }
/// Navigation property for user logins
public virtual ICollection<TLogin> Logins { get; private set; }
public virtual string UserName { get; set; }
}
上記のプロパティは、上記の表の AspNetUsers
プロパティと一致します。
の IUser
ジェネリック引数を使用すると、主キーに異なる型を使用してクラスを派生できます。 主キーを文字列から int または GUID に変更する方法を示す ChangePK サンプルを参照してください。
ApplicationUser
ApplicationUser
(public class ApplicationUserManager : UserManager<ApplicationUser>
) は Models\IdentityModels.cs で次のように定義されています。
public class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(
UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in
// CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this,
DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
}
上記の強調表示されたコードでは、 ClaimsIdentity が生成されます。 ASP.NET ID と OWIN Cookie 認証はクレーム ベースであるため、フレームワークでは、アプリでユーザーの を ClaimsIdentity
生成する必要があります。 ClaimsIdentity
には、ユーザーの名前、年齢、ユーザーが属しているロールなど、ユーザーのすべての要求に関する情報があります。 この段階で、ユーザーの要求をさらに追加することもできます。
OWIN AuthenticationManager.SignIn
メソッドは を ClaimsIdentity
渡し、ユーザーをサインインさせます。
private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
AuthenticationManager.SignIn(new AuthenticationProperties(){
IsPersistent = isPersistent },
await user.GenerateUserIdentityAsync(UserManager));
}
Facebook、Twitter、LinkedIn、Google OAuth2 のサインオンを使用した MVC 5 アプリでは、クラスに 追加のプロパティを追加する方法が ApplicationUser
示されています。
Email確認
新しいユーザーが登録したメールを確認して、他のユーザーを偽装していないことを確認することをお勧めします (つまり、他のユーザーのメールに登録していません)。 ディスカッション フォーラムがあるとします。これは、 として"joe@contoso.com"
登録できないように"bob@example.com"
したいとします。 電子メールの確認がない場合は、 "joe@contoso.com"
アプリから不要なメールを受け取る可能性があります。 Bob が誤って として "bib@example.com"
登録され、それに気付かなかったとします。アプリに正しいメールがないため、パスワードの回復を使用できないとします。 Email確認では、ボットからの保護が制限され、決定されたスパマーからの保護は提供されません。登録に使用できる多くの動作する電子メール エイリアスがあります。次のサンプルでは、ユーザーは自分のアカウントが確認されるまでパスワードを変更できません (登録したメール アカウントで受信した確認リンクを選択することによって)。この作業フローを他のシナリオに適用できます。たとえば、管理者によって作成された新しいアカウントのパスワードを確認およびリセットするためのリンクの送信、プロファイルの変更時にユーザーに電子メールを送信するなどです。 一般に、新しいユーザーが電子メール、SMS テキスト メッセージ、または別のメカニズムによって確認される前に、Web サイトにデータを投稿できないようにする必要があります。
より完全なサンプルを作成する
このセクションでは、NuGet を使用して、使用するより完全なサンプルをダウンロードします。
新しい 空 の ASP.NET Web プロジェクトを作成します。
パッケージ マネージャー コンソールで、次のコマンドを入力します。
Install-Package SendGrid Install-Package -Prerelease Microsoft.AspNet.Identity.Samples
このチュートリアルでは、 SendGrid を 使用してメールを送信します。 このパッケージは
Identity.Samples
、使用するコードをインストールします。SSL を 使用するようにプロジェクトを設定します。
アプリを実行し、[登録] リンクを選択し、 登録 フォームを投稿して、ローカル アカウントの作成をテストします。
メールの確認をシミュレートするデモ電子メール リンクを選択します。
サンプルからデモ電子メール リンクの確認コードを削除します (
ViewBag.Link
アカウント コントローラーのコード。DisplayEmail
およびForgotPasswordConfirmation
アクション メソッドと Razor ビューを参照してください。
警告
このサンプルのいずれかのセキュリティ設定を変更した場合、運用アプリは、加えられた変更を明示的に呼び出すセキュリティ監査を受ける必要があります。
App_Start\IdentityConfig.cs のコードを調べる
このサンプルでは、アカウントを作成し、管理 ロールに追加する方法を示します。 サンプルのメールは、管理者アカウントに使用するメールに置き換える必要があります。 管理者アカウントを作成する最も簡単な方法は、 メソッドでプログラムによって Seed
行います。 今後、ユーザーとロールの作成と管理を可能にするツールを提供したいと考えています。 サンプル コードでは、ユーザーとロールを作成および管理できますが、ロールとユーザー管理者ページを実行するには、まず管理者アカウントが必要です。 このサンプルでは、DB のシード処理時に管理者アカウントが作成されます。
パスワードを変更し、メール通知を受信できるアカウントに名前を変更します。
警告
セキュリティ - ソース コードに機密データを保存しないでください。
前述のように、 app.CreatePerOwinContext
スタートアップ クラスの呼び出しは、アプリ DB コンテンツ、ユーザー マネージャー、ロール マネージャー クラスのメソッドにコールバック Create
を追加します。 OWIN パイプラインは、要求ごとにこれらのクラスの メソッドを呼び出 Create
し、各クラスのコンテキストを格納します。 アカウント コントローラーは、HTTP コンテキスト (OWIN コンテキストを含む) からユーザー マネージャーを公開します。
public ApplicationUserManager UserManager
{
get
{
return _userManager ??
HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
}
ユーザーがローカル アカウントを登録すると、 メソッドが HTTP Post 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)
{
var 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 this link: <a href=\""
+ callbackUrl + "\">link</a>");
// ViewBag.Link = callbackUrl; // Used only for initial demo.
return View("DisplayEmail");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
上記のコードでは、モデル データを使用して、入力した電子メールとパスワードを使用して新しいユーザー アカウントを作成します。 メール エイリアスがデータ ストア内にある場合、アカウントの作成は失敗し、フォームが再び表示されます。 メソッドは GenerateEmailConfirmationTokenAsync
、セキュリティで保護された確認トークンを作成し、ASP.NET ID データ ストアに格納します。 Url.Action メソッドは、 と の確認トークンを含むリンクをUserId
作成します。 このリンクはユーザーに電子メールで送信され、ユーザーは自分のメール アプリのリンクを選択して自分のアカウントを確認できます。
電子メールの確認を設定する
SendGrid のサインアップ ページに移動し、無料アカウントに登録します。 SendGrid を構成するには、次のようなコードを追加します。
public class EmailService : IIdentityMessageService
{
public Task SendAsync(IdentityMessage message)
{
return configSendGridasync(message);
}
private 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)
{
return transportWeb.DeliverAsync(myMessage);
}
else
{
return Task.FromResult(0);
}
}
}
注意
Emailクライアントは、テキスト メッセージ (HTML なし) のみを頻繁に受け入れます。 メッセージはテキストと HTML で指定する必要があります。 上記の SendGrid サンプルでは、上記の コードと myMessage.Html
コードを使用してmyMessage.Text
これを行います。
次のコードは、リンクのみを返す MailMessage クラス message.Body
を使用して電子メールを送信する方法を示しています。
void sendMail(Message message)
{
#region formatter
string text = string.Format("Please click on this link to {0}: {1}", message.Subject, message.Body);
string html = "Please confirm your account by clicking this link: <a href=\"" + message.Body + "\">link</a><br/>";
html += HttpUtility.HtmlEncode(@"Or click on the copy the following link on the browser:" + message.Body);
#endregion
MailMessage msg = new MailMessage();
msg.From = new MailAddress("joe@contoso.com");
msg.To.Add(new MailAddress(message.Destination));
msg.Subject = message.Subject;
msg.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(text, null, MediaTypeNames.Text.Plain));
msg.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(html, null, MediaTypeNames.Text.Html));
SmtpClient smtpClient = new SmtpClient("smtp.gmail.com", Convert.ToInt32(587));
System.Net.NetworkCredential credentials = new System.Net.NetworkCredential("joe@contoso.com", "XXXXXX");
smtpClient.Credentials = credentials;
smtpClient.EnableSsl = true;
smtpClient.Send(msg);
}
警告
セキュリティ - ソース コードに機密データを保存しないでください。 アカウントと資格情報は appSetting に格納されます。 Azure では、これらの値をAzure portalの [構成] タブに安全に格納できます。 パスワードやその他の機密データを ASP.NET と Azure にデプロイするためのベスト プラクティスに関するページを参照してください。
SendGrid の資格情報を入力し、アプリを実行し、メール エイリアスに登録して、メールの確認リンクを選択できます。 Outlook.com メール アカウントでこれを行う方法については、「John Atten の C# SMTP Configuration for Outlook.Com SMTP Host」および「ASP.NET Identity 2.0: Setting Up Account Validation and Two-Factor Authorization posts」を参照してください。
ユーザーが [登録 ] ボタンを選択すると、検証トークンを含む確認メールがメール アドレスに送信されます。
ユーザーには、アカウントの確認トークンを含む電子メールが送信されます。
コードを確認する
次のコードは POST ForgotPassword
メソッドを示します。
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");
}
var 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 here: <a href=\"" + callbackUrl + "\">link</a>");
return View("ForgotPasswordConfirmation");
}
// If we got this far, something failed, redisplay form
return View(model);
}
ユーザーの電子メールが確認されていない場合、メソッドはサイレントモードで失敗します。 無効なメール アドレスに対してエラーが投稿された場合、悪意のあるユーザーはその情報を使用して、攻撃する有効な userId (電子メール エイリアス) を見つける可能性があります。
次のコードは、 ConfirmEmail
ユーザーが送信された電子メールの確認リンクを選択したときに呼び出されるアカウント コントローラーの メソッドを示しています。
public async Task<ActionResult> ConfirmEmail(string userId, string code)
{
if (userId == null || code == null)
{
return View("Error");
}
var result = await UserManager.ConfirmEmailAsync(userId, code);
if (result.Succeeded)
{
return View("ConfirmEmail");
}
AddErrors(result);
return View();
}
忘れたパスワード トークンが使用されると、無効になります。 メソッドの次の Create
コード変更 ( App_Start\IdentityConfig.cs ファイル内) では、トークンの有効期限が 3 時間に設定されます。
if (dataProtectionProvider != null)
{
manager.UserTokenProvider =
new DataProtectorTokenProvider<ApplicationUser>
(dataProtectionProvider.Create("ASP.NET Identity"))
{
TokenLifespan = TimeSpan.FromHours(3)
};
}
上記のコードでは、忘れたパスワードと電子メール確認トークンは 3 時間で期限切れになります。 既定値 TokenLifespan
は 1 日です。
次のコードは、電子メールの確認方法を示しています。
// GET: /Account/ConfirmEmail
[AllowAnonymous]
public async Task<ActionResult> ConfirmEmail(string userId, string code)
{
if (userId == null || code == null)
{
return View("Error");
}
IdentityResult result;
try
{
result = await UserManager.ConfirmEmailAsync(userId, code);
}
catch (InvalidOperationException ioe)
{
// ConfirmEmailAsync throws when the userId is not found.
ViewBag.errorMessage = ioe.Message;
return View("Error");
}
if (result.Succeeded)
{
return View();
}
// If we got this far, something failed.
AddErrors(result);
ViewBag.errorMessage = "ConfirmEmail failed";
return View("Error");
}
アプリの安全性を高めるために、ASP.NET Identity ではTwo-Factor認証 (2FA) がサポートされています。 「ASP.NET ID 2.0: アカウント検証の設定」と「John Atten による承認のTwo-Factor」を参照してください。 ログイン パスワードの試行エラーにアカウント ロックアウトを設定できますが、この方法では、ログインが DOS ロックアウトの影響を受けやすくなります。 アカウントロックアウトは 2FA でのみ使用することをお勧めします。
その他のリソース
- ASP.NET Identity のカスタム ストレージ プロバイダーの概要
- Facebook、Twitter、LinkedIn、Google OAuth2 のサインオンを使用した MVC 5 アプリでは、 ユーザー テーブルにプロファイル情報を追加する方法も示されています。
- ASP.NET MVC と ID 2.0: John Atten による基本の理解。
- ASP.NET Identity 入門
- プラナヴ・ラストギによる ASP.NET Identity 2.0.0 の RTM の発表。
フィードバック
https://aka.ms/ContentUserFeedback」を参照してください。
以下は間もなく提供いたします。2024 年を通じて、コンテンツのフィードバック メカニズムとして GitHub の issue を段階的に廃止し、新しいフィードバック システムに置き換えます。 詳細については、「フィードバックの送信と表示