ASP.NET Identity で SMS と電子メールを利用して 2 要素認証を行う

作成者: Hao Kungプラナヴ ラストギRick AndersonSuhas Joshi

このチュートリアルでは、SMS と電子メールを使用して 2 要素認証 (2FA) を設定する方法について説明します。

この記事は、Rick Anderson (@RickAndMSFT)、プラナヴ ラストギ (@rustd)、Hao Kung、Suhas Joshi によって執筆されました。 NuGet サンプルは、主に Hao Kung によって記述されました。

このトピックでは、以下の内容を説明します。

ID サンプルのビルド

このセクションでは、NuGet を使用して、使用するサンプルをダウンロードします。 まず、Visual Studio Express 2013 for Web または Visual Studio 2013をインストールして実行します。 Visual Studio 2013 Update 2 以降をインストールします。

注意

警告: このチュートリアルを完了するには、Visual Studio 2013 Update 2 をインストールする必要があります。

  1. 新しい の ASP.NET Web プロジェクトを作成します。

  2. パッケージ マネージャー コンソールで、次のコマンドを入力します。

    Install-Package SendGrid
    Install-Package -Prerelease Microsoft.AspNet.Identity.Samples

    このチュートリアルでは、 SendGrid を使用してメールを送信し、SMS テキストを送信するために Twilio または ASPSMS を使用します。 このパッケージは Identity.Samples 、使用するコードをインストールします。

  3. SSL を 使用するようにプロジェクトを設定します。

  4. 省略可能: Email確認チュートリアルの手順に従って SendGrid をフックし、アプリを実行して電子メール アカウントを登録します。

  5. オプション: サンプルからデモ電子メール リンクの確認コードを削除します ( ViewBag.Link アカウント コントローラーのコード。 DisplayEmail および ForgotPasswordConfirmation アクション メソッドと Razor ビューを参照してください。

  6. オプション: コードを ViewBag.Status 管理コントローラーとアカウント コントローラーから削除し、 Views\Account\VerifyCode.cshtml および Views\Manage\VerifyPhoneNumber.cshtml razor ビューから削除します。 または、ディスプレイを ViewBag.Status 維持して、このアプリがローカルでどのように動作するかをテストできます。メールや SMS メッセージを接続して送信する必要はありません。

注意

警告: このサンプルのいずれかのセキュリティ設定を変更した場合、運用アプリは、加えられた変更を明示的に呼び出すセキュリティ監査を受ける必要があります。

2 要素認証用に SMS を設定する

このチュートリアルでは、Twilio または ASPSMS を使用する手順を説明しますが、他の SMS プロバイダーを使用できます。

  1. SMS プロバイダーを使用したユーザー アカウントの作成

    Twilio または ASPSMS アカウントを作成します。

  2. 追加のパッケージのインストールまたはサービス参照の追加

    Twilio:
    パッケージ マネージャー コンソールで、次のコマンドを入力します。
    Install-Package Twilio

    ASPSMS:
    次のサービス参照を追加する必要があります。

    サービス参照の追加ウィンドウの画像

    Address:
    https://webservice.aspsms.com/aspsmsx2.asmx?WSDL

    名前空間:
    ASPSMSX2

  3. SMS プロバイダーユーザーの資格情報の把握

    Twilio:
    Twilio アカウントの [ ダッシュボード ] タブから、 アカウント SID認証トークンをコピーします。

    ASPSMS:
    アカウント設定から[Userkey]\( ユーザーキー\) に移動し、自己定義パスワードと共にコピー します

    これらの値は、後で 変数 SMSAccountIdentificationSMSAccountPassword に格納します。

  4. SenderID または Originator の指定

    Twilio:
    [ 番号 ] タブで、Twilio の電話番号をコピーします。

    ASPSMS:
    [ 元のユーザーのロック解除 ] メニューで、1 つ以上の発信元のロックを解除するか、英数字の発信元を選択します (すべてのネットワークでサポートされていません)。

    この値は後で 変数 SMSAccountFrom に格納します。

  5. 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: ソース管理からプライベート設定を保持するを参照してください。

  6. SMS プロバイダーへのデータ転送の実装

    SmsServiceApp_Start\IdentityConfig.cs ファイルで クラスを構成します。

    使用されている 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
        }
    }
    
  7. アプリを実行し、以前に登録したアカウントでログインします。

  8. ユーザー ID をクリックすると、コントローラーでManageアクション メソッドがIndexアクティブになります。

    アプリにログインしている登録済みアカウントの画像

  9. [追加] をクリックします。

    電話番号の追加リンクの画像

  10. 数秒後に、確認コードを含むテキスト メッセージが表示されます。 それを入力し、[ 送信] を押します

    電話認証コードエントリを示す画像

  11. [管理] ビューには、電話番号が追加されたことが表示されます。

    電話番号を示す管理ビュー ウィンドウの画像

コードを確認する

// 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 、投稿されたセキュリティ コードをチェックします。 コードが正しい場合は、電話番号がテーブルのフィールドにPhoneNumberAspNetUsers追加されます。 その呼び出しが成功した場合、 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 は、テーブル (または Identity DB 内の他の場所) に格納 AspNetUsers されません。 セキュリティ Cookie トークンは DPAPI を使用して自己署名され、 と の有効期限情報を UserId, SecurityStamp 使用して作成されます。

Cookie ミドルウェアは、各要求で Cookie をチェックします。 クラスの メソッドは SecurityStampValidator DB にヒットし、 で指定されているように、定期的にセキュリティ スタンプをチェックしますvalidateIntervalStartup これは、セキュリティ プロファイルを変更しない限り、(サンプルでは) 30 分ごとに発生します。 データベースへの乗車を最小限に抑えるために、30 分の間隔が選択されました。

SignInAsyncセキュリティ プロファイルに変更が加えられた場合は、 メソッドを呼び出す必要があります。 セキュリティ プロファイルが変更されると、データベースは フィールドをSecurityStamp更新します。メソッドをSignInAsync呼び出さずに、OWIN パイプラインが次にデータベース (validateInterval) にヒットするまでログインし続けます。 これをテストするには、 メソッドを 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);

上記のコードが変更されると、セキュリティ プロファイルを変更できます (たとえば、 Two Factor Enabled の状態を変更するなど)。 メソッドが失敗すると SecurityStampValidator.OnValidateIdentity 、5 秒でログアウトされます。 メソッドの戻り行を SignInAsync 削除し、別のセキュリティ プロファイルを変更すると、ログアウトされません。メソッドは SignInAsync 、新しいセキュリティ Cookie を生成します。

2 要素認証を有効にする

サンプル アプリでは、UI を使用して 2 要素認証 (2FA) を有効にする必要があります。 2FA を有効にするには、ナビゲーション バーでユーザー ID (電子メール エイリアス) をクリックします。2 要素認証を有効にする U I の画像
[2FA を有効にする] をクリックします。2 要素認証の有効化リンクを示すユーザー ID をクリックした後の画像 ログアウトしてから、再度ログインします。 メールを有効にしている場合 (前の チュートリアルを参照)、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");
}

SignInAsync enable 2FA はセキュリティ プロファイルの変更であるため、 を呼び出す必要があることに注意してください。 2FA が有効になっている場合、ユーザーは登録した 2FA アプローチ (サンプルの SMS と電子メール) を使用して、2FA を使用してログインする必要があります。

QR コード ジェネレーターなどの 2FA プロバイダーをさらに追加することも、独自のプロバイダーを作成することもできます。

注意

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 true に変更shouldLockoutすることでログインのロックアウトを有効にすることができますが、アカウントが 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 メソッドで作成されます。 SelectListItemDropDownListFor ヘルパーに渡されます。これにより、ユーザーは 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を設定できます)。

その他のリソース