ASP.NET Identity で SMS と電子メールを利用して 2 要素認証を行う
著者: Hao Kung、Pranav Rastogi、Rick Anderson、Suhas Joshi
このチュートリアルでは、SMS とメールを使用して 2 要素認証 (2FA) を設定する方法について説明します。
この記事の著者は Rick Anderson (@RickAndMSFT)、Pranav Rastogi (@rustd)、Hao Kung、Suhas Joshi です。 NuGet サンプルは、主に Hao Kung によって記述されました。
このトピックでは、以下の内容を説明します。
- Identity サンプルのビルド
- 2 要素認証用に SMS を設定する
- 2 要素認証を有効にする
- 2 要素認証プロバイダーを登録する方法
- ソーシャルおよびローカル ログイン アカウントを結合する
- ブルート フォース攻撃からのアカウント ロックアウト
Identity サンプルのビルド
このセクションでは、NuGet を使用して、これから先で扱うサンプルをダウンロードします。 まず、Visual Studio Express 2013 for Web または Visual Studio 2013 をインストールして実行します。 Visual Studio 2013 Update 2 以上をインストールします。
Note
警告: このチュートリアルを完了するには、Visual Studio 2013 Update 2 をインストールする必要があります。
新しい空の ASP.NET Web プロジェクトを作成します。
パッケージ マネージャー コンソールで、以下のコマンドを入力します。
Install-Package SendGrid
Install-Package -Prerelease Microsoft.AspNet.Identity.Samples
このチュートリアルでは、メール送信には SendGrid を、SMS テキスト送信には Twilio または ASPSMS を使用します。
Identity.Samples
パッケージは、使用するコードをインストールします。SSL を使用するようにプロジェクトを設定します。
"省略可能": 「メール確認チュートリアル」の指示に従って SendGrid をフックした後、アプリを実行してメール アカウントを登録します。
"省略可能:" サンプルからデモのメール リンク確認コードを削除します (アカウント コントローラー内の
ViewBag.Link
コードです。DisplayEmail
とForgotPasswordConfirmation
の各アクション メソッドと Razor ビューを参照してください)。"省略可能:" Manage および Account コントローラー、そして Views\Account\VerifyCode.cshtml および Views\Manage\VerifyPhoneNumber.cshtml razor ビューから
ViewBag.Status
コードを削除します。 代わりに、フックを行わずにこのアプリがどのように動作しているかをローカルでテストしてメールと SMS メッセージを送信するためにViewBag.Status
表示を残しても構いません。
Note
警告: このサンプルのいずれかのセキュリティ設定を変更した場合、運用アプリは、加えられた変更を明示的に呼び出すセキュリティ監査を受ける必要があります。
2 要素認証用に SMS を設定する
このチュートリアルでは、Twilio または ASPSMS のいずれかを使用する手順について説明しますが、他の SMS プロバイダーを使用することもできます。
SMS プロバイダーを使用したユーザー アカウントの作成
追加パッケージのインストールまたはサービス参照の追加
Twilio:
パッケージ マネージャー コンソールで、次のコマンドを入力します。
Install-Package Twilio
ASPSMS:
次のサービス参照を追加する必要があります。[アドレス]:
https://webservice.aspsms.com/aspsmsx2.asmx?WSDL
名前空間:
ASPSMSX2
SMS プロバイダーのユーザーの資格情報を確認する
Twilio:
Twilio アカウントの [ダッシュボード] タブの [Account SID]\(アカウント SID\) と [Auth token]\(セキュリティ トークン\) をコピーします。ASPSMS:
アカウント設定から [Userkey] に移動し、それを自分で設定したパスワードと共にコピーします。これらの値は後で変数
SMSAccountIdentification
とSMSAccountPassword
内に保存します。SenderID または Originator の指定
Twilio:
[Numbers]\(数値\) タブで、Twilio の電話番号をコピーします。ASPSMS:
[Unlock Originators]\(発信元のロック解除\) メニューで、1 つまたは複数の発信元のロックを解除するか、英数字の発信元を選択します (すべてのネットワークではサポートされていません)。この値は後で変数
SMSAccountFrom
内に保存します。SMS プロバイダーの資格情報をアプリへ転送
以下のようにアプリが資格情報と送信者の電話番号を利用できるようにします。
public static class Keys { public static string SMSAccountIdentification = "My Idenfitication"; public static string SMSAccountPassword = "My Password"; public static string SMSAccountFrom = "+15555551234"; }
警告
セキュリティ - 機密データをソース コード内に保存しないでください。 アカウントと資格情報を上記のコードに追加したのは、サンプルをわかりやすくするためです。 Jon Atten による「ASP.NET MVC: プライベート設定をソース管理に含めない」を参照してください。
SMS プロバイダーへのデータ転送の実装
App_Start\IdentityConfig.cs ファイルで
SmsService
クラスを構成します。使用する SMS プロバイダーに応じて、Twilio または ASPSMS セクションをアクティブにします。
public class SmsService : IIdentityMessageService { public Task SendAsync(IdentityMessage message) { // Twilio Begin // var Twilio = new TwilioRestClient( // Keys.SMSAccountIdentification, // Keys.SMSAccountPassword); // var result = Twilio.SendMessage( // Keys.SMSAccountFrom, // message.Destination, message.Body // ); // Status is one of Queued, Sending, Sent, Failed or null if the number is not valid // Trace.TraceInformation(result.Status); // Twilio doesn't currently have an async API, so return success. // return Task.FromResult(0); // Twilio End // ASPSMS Begin // var soapSms = new WebApplication1.ASPSMSX2.ASPSMSX2SoapClient("ASPSMSX2Soap"); // soapSms.SendSimpleTextSMS( // Keys.SMSAccountIdentification, // Keys.SMSAccountPassword, // message.Destination, // Keys.SMSAccountFrom, // message.Body); // soapSms.Close(); // return Task.FromResult(0); // ASPSMS End } }
アプリを実行し、以前に登録したアカウントでログインします。
ユーザー ID をクリックすると、
Manage
コントローラーでIndex
アクション メソッドがアクティブになります。追加をクリックします。
電話番号を入力すると数秒後に、確認コードを含むテキスト メッセージが送信されます。 確認コードを入力し、[送信] を押します。
管理ビューには、電話番号が追加されたと表示されます。
コードを確認する
// GET: /Account/Index
public async Task<ActionResult> Index(ManageMessageId? message)
{
ViewBag.StatusMessage =
message == ManageMessageId.ChangePasswordSuccess ? "Your password has been changed."
: message == ManageMessageId.SetPasswordSuccess ? "Your password has been set."
: message == ManageMessageId.SetTwoFactorSuccess ? "Your two factor provider has been set."
: message == ManageMessageId.Error ? "An error has occurred."
: message == ManageMessageId.AddPhoneSuccess ? "The phone number was added."
: message == ManageMessageId.RemovePhoneSuccess ? "Your phone number was removed."
: "";
var model = new IndexViewModel
{
HasPassword = HasPassword(),
PhoneNumber = await UserManager.GetPhoneNumberAsync(User.Identity.GetUserId()),
TwoFactor = await UserManager.GetTwoFactorEnabledAsync(User.Identity.GetUserId()),
Logins = await UserManager.GetLoginsAsync(User.Identity.GetUserId()),
BrowserRemembered = await AuthenticationManager.TwoFactorBrowserRememberedAsync(User.Identity.GetUserId())
};
return View(model);
}
Manage
コントローラーの Index
アクション メソッドは、前回のアクションに基づいて状態メッセージを設定し、ローカル パスワードを変更したり、ローカル アカウントを追加したりするためのリンクを提供します。 Index
メソッドは、2FA 電話番号、外部ログイン、2FA の有効化、および "このブラウザーで 2FA を記憶する" (これについては後で説明します) の状態も表示します。 タイトル バーでユーザー ID (メール) をクリックしても、メッセージは渡されません。 [電話番号 : 削除] リンクをクリックすると、Message=RemovePhoneSuccess
がクエリ文字列として渡されます。
https://localhost:44300/Manage?Message=RemovePhoneSuccess
[]
AddPhoneNumber
アクション メソッドは、SMS メッセージを受信できる電話番号を入力するダイアログ ボックスを表示します。
// GET: /Account/AddPhoneNumber
public ActionResult AddPhoneNumber()
{
return View();
}
[確認コードの送信] ボタンをクリックすると、電話番号が HTTP POST AddPhoneNumber
アクション メソッドにポストされます。
// POST: /Account/AddPhoneNumber
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> AddPhoneNumber(AddPhoneNumberViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
// Generate the token
var code = await UserManager.GenerateChangePhoneNumberTokenAsync(
User.Identity.GetUserId(), model.Number);
if (UserManager.SmsService != null)
{
var message = new IdentityMessage
{
Destination = model.Number,
Body = "Your security code is: " + code
};
// Send token
await UserManager.SmsService.SendAsync(message);
}
return RedirectToAction("VerifyPhoneNumber", new { PhoneNumber = model.Number });
}
GenerateChangePhoneNumberTokenAsync
メソッドは、SMS メッセージ内に設定されることになるセキュリティ トークンを生成します。 SMS サービスが構成済みである場合、トークンは "Your security code is <token>" という文字列として送信されます。 SmsService.SendAsync
メソッドが非同期的に呼び出された後、アプリは (次のダイアログを表示する) VerifyPhoneNumber
アクション メソッドにリダイレクトされ、そこでユーザーは確認コードを入力することができます。
コードを入力して送信をクリックすると、コードは HTTP POST VerifyPhoneNumber
アクション メソッドにポストされます。
// POST: /Account/VerifyPhoneNumber
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> VerifyPhoneNumber(VerifyPhoneNumberViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var result = await UserManager.ChangePhoneNumberAsync(User.Identity.GetUserId(), model.PhoneNumber, model.Code);
if (result.Succeeded)
{
var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
if (user != null)
{
await SignInAsync(user, isPersistent: false);
}
return RedirectToAction("Index", new { Message = ManageMessageId.AddPhoneSuccess });
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "Failed to verify phone");
return View(model);
}
ChangePhoneNumberAsync
メソッドは、ポストされたセキュリティ コードをチェックします。 コードが正しい場合は、その電話番号が AspNetUsers
テーブルの PhoneNumber
フィールドに追加されます。 その呼び出しが成功すると、以下の SignInAsync
メソッドが呼び出されます。
private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
// Clear the temporary cookies used for external and two factor sign ins
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie,
DefaultAuthenticationTypes.TwoFactorCookie);
AuthenticationManager.SignIn(new AuthenticationProperties
{
IsPersistent = isPersistent
},
await user.GenerateUserIdentityAsync(UserManager));
}
isPersistent
パラメーターは、認証セッションが複数の要求にわたって保持されるかどうかを設定します。
セキュリティ プロファイルを変更すると、新しいセキュリティ スタンプが生成され、AspNetUsers テーブルの SecurityStamp
フィールドに格納されます。 SecurityStamp
フィールドはセキュリティ Cookie と異なることに注意してください。 セキュリティ Cookie は、AspNetUsers
テーブル (または Identity DB 内の他の場所) には格納されません。 セキュリティ Cookie トークンは DPAPI を使用して自己署名され、UserId, SecurityStamp
と有効期限情報と共に作成されます。
Cookie ミドルウェアは、各要求で Cookie をチェックします。 Startup
クラス内の SecurityStampValidator
メソッドは DB にヒットし、セキュリティ スタンプを validateInterval
に指定されたとおりに定期的にチェックします。 これは、セキュリティ プロファイルを変更しない限り、(このサンプルでは) 30 分ごとに行われます。 この 30 分間隔は、データベースとの往復を最小限に抑えるために選択されました。
セキュリティ プロファイルに何らかの変更が加えられた場合は、SignInAsync
メソッドを呼び出す必要があります。 セキュリティ プロファイルが変更されると、データベースは SecurityStamp
フィールドを更新するため、SignInAsync
メソッドを呼び出さずにログインしたままでいられるのは、次に OWIN パイプラインがデータベースに到達するまで (つまり validateInterval
の間) "だけ" です。 以下のように、すぐに return するように SignInAsync
メソッドを変更し、Cookie の validateInterval
プロパティを 30 分から 5 秒に設定することで、これをテストすることができます。
private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
return;
// Clear any partial cookies from external or two factor partial sign ins
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie,
DefaultAuthenticationTypes.TwoFactorCookie);
AuthenticationManager.SignIn(new AuthenticationProperties
{
IsPersistent = isPersistent
},
await user.GenerateUserIdentityAsync(UserManager));
}
public void ConfigureAuth(IAppBuilder app) {
// Configure the db context, user manager and role manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a
// third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions {
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider {
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add
// an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
//validateInterval: TimeSpan.FromMinutes(30),
validateInterval: TimeSpan.FromSeconds(5),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
上記のようにコードを変更した上で、(たとえば、[2 要素の有効化] の状態を変更することで) セキュリティ プロファイルを変更すると、SecurityStampValidator.OnValidateIdentity
メソッドが失敗してから 5 秒後にログアウトされることになります。 SignInAsync
メソッド内の return 行を削除すると、別のセキュリティ プロファイルの変更を行っても、ログアウトはされなくなります。SignInAsync
メソッドは、新しいセキュリティ Cookie を生成します。
2 要素認証を有効化する
サンプル アプリでは、UI を使用して 2 要素認証 (2FA) を有効にする必要があります。 2FA を有効にするには、ナビゲーション バーでユーザー ID (メール エイリアス) をクリックします。
[2FA を有効にする] をクリックします。 ログアウトしてから、ログインし直します。 メールを有効にしている場合 (前回のチュートリアルを参照)、2FA に使用する SMS またはメールを選択できます。 (SMS またはメールからの) コードを入力できる [コードの確認] ページが表示されます。[このブラウザーを記憶する] チェック ボックスをクリックすることで、そのコンピューターとブラウザーでログオンするために 2FA を使用する必要がなくなります。 2FA を有効にし、[このブラウザーを記憶する] をクリックすると、あなたのアカウントにアクセスしようとしている悪意のあるユーザーからの 2FA による強力な保護が提供されます (これは、そのユーザーがあなたのコンピューターへのアクセス権を持っていない場合に限ります)。 これは、定期的に使用するすべてのプライベート マシンで実行できます。 [このブラウザーを記憶する] を設定することで、普段は使用しないコンピューターに 2FA というセキュリティを追加すると同時に、自身のコンピューターで 2FA を行う必要がないという利便性も達成できます。
2 要素認証プロバイダーを登録する方法
新しい MVC プロジェクトを作成する際には、IdentityConfig.cs ファイルには、2 要素認証プロバイダーを登録するための次のコードが含まれることになります。
public static ApplicationUserManager Create(
IdentityFactoryOptions<ApplicationUserManager> options,
IOwinContext context)
{
var manager = new ApplicationUserManager(
new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
// Configure validation logic for usernames
manager.UserValidator = new UserValidator<ApplicationUser>(manager)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
// Configure validation logic for passwords
manager.PasswordValidator = new PasswordValidator
{
RequiredLength = 6,
RequireNonLetterOrDigit = true,
RequireDigit = true,
RequireLowercase = true,
RequireUppercase = true,
};
// Register two factor authentication providers. This application uses Phone and Emails as a
// step of receiving a code for verifying the user
// You can write your own provider and plug it in here.
manager.RegisterTwoFactorProvider("PhoneCode", new PhoneNumberTokenProvider<ApplicationUser>
{
MessageFormat = "Your security code is: {0}"
});
manager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider<ApplicationUser>
{
Subject = "Security Code",
BodyFormat = "Your security code is: {0}"
});
manager.EmailService = new EmailService();
manager.SmsService = new SmsService();
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>
(dataProtectionProvider.Create("ASP.NET Identity"));
}
return manager;
}
2FA 用の電話番号を追加する
Manage
コントローラー内の AddPhoneNumber
アクション メソッドは、セキュリティ トークンを生成し、それをユーザーが指定した電話番号に送信します。
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> AddPhoneNumber(AddPhoneNumberViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
// Generate the token and send it
var code = await UserManager.GenerateChangePhoneNumberTokenAsync(
User.Identity.GetUserId(), model.Number);
if (UserManager.SmsService != null)
{
var message = new IdentityMessage
{
Destination = model.Number,
Body = "Your security code is: " + code
};
await UserManager.SmsService.SendAsync(message);
}
return RedirectToAction("VerifyPhoneNumber", new { PhoneNumber = model.Number });
}
トークンの送信後には、これは VerifyPhoneNumber
メソッドへのリダイレクトを行い、そこではコードを入力することで 2FA 用の SMS を登録できます。 SMS 2FA は、電話番号の確認が完了するまで使用されません。
2FA の有効化
EnableTFA
アクション メソッドは、以下のように 2FA を有効にします。
// POST: /Manage/EnableTFA
[HttpPost]
public async Task<ActionResult> EnableTFA()
{
await UserManager.SetTwoFactorEnabledAsync(User.Identity.GetUserId(), true);
var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
if (user != null)
{
await SignInAsync(user, isPersistent: false);
}
return RedirectToAction("Index", "Manage");
}
2FA の有効化はセキュリティ プロファイルに対する変更であるため、SignInAsync
を呼び出す必要があることに注意してください。 2FA が有効にされると、ユーザーは 2FA を使用してログインしなくてはならなくなり、自分が登録した 2FA の手法 (サンプルでは SMS とメール) を使用することになります。
QR コード ジェネレーターなどの 2FA プロバイダーをさらに追加することも、独自のプロバイダーを記述することもできます。
Note
2FA コードは時間ベースのワンタイム パスワード アルゴリズムを使用して生成され、そのコードは 6 分間有効です。 コードの入力に 6 分以上かかった場合は、無効なコード エラー メッセージが表示されます。
ソーシャルとローカルのログイン アカウントを結合する
メールのリンクをクリックして、ローカル アカウントとソーシャル アカウントを組み合わせることができます。 以下のシーケンスでは、"RickAndMSFT@gmail.com" は最初からローカル ログインとして作成されますが、まずはアカウントをソーシャル ログインとして作成してから、ローカル ログインを追加することもできます。
[管理] リンクをクリックします。 このアカウントに関連付けられている外部 (ソーシャル ログイン) が 0 であることに注意してください。
別のログイン サービスへのリンクを選択し、アプリの要求を受け入れます。 2 つのアカウントが組み合わされているため、どちらのアカウントでもログオンできます。 ソーシャル ログイン認証サービスがダウンしたときのため、またはさらに可能性が高いのはソーシャル アカウントにアクセスできなくなったときのために、ユーザーにローカル アカウントを追加させたいことがあります。
次の図では、Tom はソーシャル ログインです (ページに表示されている [外部ログイン: 1] から確認できます)。
[パスワードの選択] を選択すると、同じアカウントに関連付けられているローカル ログオンを追加できます。
ブルート フォース攻撃からのアカウント ロックアウト
ユーザー ロックアウトを有効にすることで、アプリ上のアカウントを辞書攻撃から保護できます。 ApplicationUserManager Create
メソッド内の次のコードは、ロックアウトを構成します。
// Configure user lockout defaults
manager.UserLockoutEnabledByDefault = true;
manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
manager.MaxFailedAccessAttemptsBeforeLockout = 5;
上記のコードは、2 要素認証に対してのみロックアウトを有効にします。 アカウント コントローラーの Login
メソッド内で shouldLockout
を true に変更することで、ログインに対してロックアウトを有効にできますが、それによってアカウントが DOS ログイン攻撃を受けやすくなるので、ログインに対してはロックアウトを有効にしないことをお勧めします。 サンプル コードでは、以下のように ApplicationDbInitializer Seed
メソッド内で作成された管理者アカウントに対してロックアウトを無効にしています。
public static void InitializeIdentityForEF(ApplicationDbContext db)
{
var userManager = HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();
var roleManager = HttpContext.Current.GetOwinContext().Get<ApplicationRoleManager>();
const string name = "admin@example.com";
const string roleName = "Admin";
//Create Role Admin if it does not exist
var role = roleManager.FindByName(roleName);
if (role == null)
{
role = new IdentityRole(roleName);
var roleresult = roleManager.Create(role);
}
var user = userManager.FindByName(name);
if (user == null)
{
user = new ApplicationUser { UserName = name, Email = name };
var result = userManager.Create(user, GetSecurePassword());
result = userManager.SetLockoutEnabled(user.Id, false);
}
// Add user admin to Role Admin if not already added
var rolesForUser = userManager.GetRoles(user.Id);
if (!rolesForUser.Contains(role.Name))
{
var result = userManager.AddToRole(user.Id, role.Name);
}
}
検証済みのメール アカウントを作成することをユーザーに要求する
次のコードは、ログインの前に検証済みのメール アカウントを作成することをユーザーに要求しています。
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 doen't count login failures towards lockout only two factor authentication
// To enable password failures to trigger 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 });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
SignInManager が 2FA 要件を確認する方法
ローカル ログインとソーシャル ログインは両方とも、2FA が有効になっているかどうかを確認するためのチェックを行います。 2FA が有効になっている場合、SignInManager
ログオン メソッドは SignInStatus.RequiresVerification
を返し、ユーザーは SendCode
アクション メソッドにリダイレクトされます。そこでユーザーはログイン シーケンスを完了するためにコードを入力しなくてはならなくなります。 ユーザーがユーザー ローカル Cookie に RememberMe を設定している場合、SignInManager
は SignInStatus.Success
を返すため、ユーザーは 2FA を実行する必要がなくなります。
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 doen't count login failures towards lockout only two factor authentication
// To enable password failures to trigger 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 });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Login");
}
// Sign in the user with this external login provider if the user already has a login
var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl });
case SignInStatus.Failure:
default:
// If the user does not have an account, then prompt the user to create an account
ViewBag.ReturnUrl = returnUrl;
ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = loginInfo.Email });
}
}
次のコードは SendCode
アクション メソッドを示しています。 SelectListItem は、ユーザーに対して有効になっているすべての 2FA メソッドを持つように作成されます。 この SelectListItem が DropDownListFor ヘルパーに渡されることで、ユーザーは 2FA の手法 (通常はメールと SMS) を選択できるようになります。
public async Task<ActionResult> SendCode(string returnUrl)
{
var userId = await SignInManager.GetVerifiedUserIdAsync();
if (userId == null)
{
return View("Error");
}
var userFactors = await UserManager.GetValidTwoFactorProvidersAsync(userId);
var factorOptions = userFactors.Select(purpose => new SelectListItem { Text = purpose, Value = purpose }).ToList();
return View(new SendCodeViewModel { Providers = factorOptions, ReturnUrl = returnUrl });
}
ユーザーが 2FA の手法をポストすると、HTTP POST SendCode
アクション メソッドが呼び出され、SignInManager
が 2FA コードを送信し、ユーザーは VerifyCode
アクション メソッドにリダイレクトされ、そこでコードを入力してログインを完了できます。
//
// POST: /Account/SendCode
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> SendCode(SendCodeViewModel model)
{
if (!ModelState.IsValid)
{
return View();
}
// Generate the token and send it
if (!await SignInManager.SendTwoFactorCodeAsync(model.SelectedProvider))
{
return View("Error");
}
return RedirectToAction("VerifyCode", new { Provider = model.SelectedProvider, ReturnUrl = model.ReturnUrl });
}
2FA ロックアウト
ログイン パスワードの試行エラーにアカウント ロックアウトを設定することはできますが、この方法では、ログインが DOS ロックアウトの影響を受けやすくなります。 アカウント ロックアウトは 2FA でのみ使用することをお勧めします。 ApplicationUserManager
が作成されると、サンプル コードは 2FA ロックアウトを設定し、MaxFailedAccessAttemptsBeforeLockout
を 5 に設定します。 ユーザーが (ローカル アカウントまたはソーシャル アカウントを通して) ログインを行うと、2FA での失敗した試行がすべて保存され、最大試行回数に達した場合、ユーザーは 5 分間ロックアウトされます (ロックアウト時間は DefaultAccountLockoutTimeSpan
で設定できます)。
その他のリソース
- ASP.NET Identity の推奨リソース Identity に関するブログ、ビデオ、チュートリアル、優れた SO リンクの完全な一覧。
- Facebook、Twitter、LinkedIn、Google OAuth2 のサインオンを使用した MVC 5 アプリでは、プロファイル情報をユーザー テーブルに追加する方法も示します。
- John Atten 著「ASP.NET MVC と Identity 2.0: 基本について」。
- ASP.NET Identity でのアカウントの確認とパスワードの回復
- ASP.NET Identity 入門
- Pranav Rastogi 著 ASP.NET Identity 2.0.0 の RTM の発表。
- ASP.NET Identity 2.0: アカウント検証と 2 要素認可の設定 (著者: John Atten)。