Поделиться через


Двухфакторная проверка подлинности с помощью SMS и электронной почты в ASP.NET Identity

Хао Кунг (Hao Kung), Пранав Растоги (Pranav Rastogi),Рик Андерсон (Rick Anderson),Сухас Джоши (Suhas Joshi)

В этом руководстве показано, как настроить двухфакторную проверку подлинности (2FA) с помощью SMS и электронной почты.

Эта статья была написана Рик Андерсон (@RickAndMSFT), Пранав Растоги (@rustd), Хао Кунг и Сухас Джоши. Пример NuGet был написан в основном Хао Кунгом.

В этой статье рассматриваются следующие вопросы:

Создание примера удостоверения

В этом разделе вы будете использовать NuGet для скачивания примера, с которым мы будем работать. Начните с установки и запуска Visual Studio Express 2013 для Web или Visual Studio 2013. Установите Visual Studio 2013 с обновлением 2 или более поздней версии.

Примечание

Предупреждение. Для работы с этим руководством необходимо установить Visual Studio 2013 с обновлением 2 .

  1. Создайте пустой веб-проект ASP.NET.

  2. В консоли диспетчера пакетов введите следующие команды:

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

    В этом руководстве мы будем использовать SendGrid для отправки электронной почты и Twilio или ASPSMS для отправки текстовых сообщений. Пакет Identity.Samples устанавливает код, с которым мы будем работать.

  3. Задайте для проекта использование SSL.

  4. Необязательно. Следуйте инструкциям в руководстве по подтверждению Email, чтобы подключить SendGrid, а затем запустить приложение и зарегистрировать учетную запись электронной почты.

  5. Дополнительные: Удалите код подтверждения демонстрационной ссылки электронной почты из примера (код ViewBag.Link в контроллере учетной записи. См. DisplayEmail методы действия и ForgotPasswordConfirmation представления Razor .

  6. Дополнительные:ViewBag.Status Удалите код из контроллеров управления и учетных записей, а также из представлений Razor Views\Account\VerifyCode.cshtml и Views\Manage\VerifyPhoneNumber.cshtml . Кроме того, вы можете сохранить ViewBag.Status дисплей, чтобы проверить, как это приложение работает локально без необходимости подключения и отправки сообщений электронной почты и SMS.

Примечание

Предупреждение. Если изменить какой-либо из параметров безопасности в этом примере, рабочим приложениям потребуется пройти аудит безопасности, который явно вызывает внесенные изменения.

Настройка SMS для двухфакторной проверки подлинности

В этом руководстве содержатся инструкции по использованию Twilio или ASPSMS, но вы можете использовать любой другой поставщик SMS.

  1. Создание учетной записи пользователя с помощью поставщика SMS

    Создайте учетную запись Twilio или ASPSMS .

  2. Установка дополнительных пакетов или добавление ссылок на службы

    Twilio:
    В консоли диспетчера пакетов введите следующую команду.
    Install-Package Twilio

    ASPSMS:
    Необходимо добавить следующую ссылку на службу:

    Изображение окна добавления ссылки на службу

    Адрес:
    https://webservice.aspsms.com/aspsmsx2.asmx?WSDL

    Пространство имен:
    ASPSMSX2

  3. Определение учетных данных пользователя поставщика SMS

    Twilio:
    На вкладке Панель мониторинга учетной записи Twilio скопируйте идентификатор безопасности учетной записи и маркер проверки подлинности.

    ASPSMS:
    В параметрах учетной записи перейдите в раздел Userkey и скопируйте его вместе со своим самоопределитым паролем.

    Позже эти значения будут сохранены в переменных SMSAccountIdentification и SMSAccountPassword .

  4. Указание SenderID или инициатора

    Twilio:
    На вкладке Числа скопируйте свой номер телефона Twilio.

    ASPSMS:
    В меню Разблокировать источники разблокируйте один или несколько источников или выберите буквенно-цифровой инициатор (не поддерживается всеми сетями).

    Позже мы будем хранить это значение в переменной SMSAccountFrom .

  5. Передача учетных данных поставщика SMS в приложение

    Сделайте учетные данные и номер телефона отправителя доступными для приложения:

    public static class Keys
    {
       public static string SMSAccountIdentification = "My Idenfitication";
       public static string SMSAccountPassword = "My Password";
       public static string SMSAccountFrom = "+15555551234";
    }
    

    Предупреждение

    Безопасность. Никогда не храните конфиденциальные данные в исходном коде. Учетная запись и учетные данные добавляются в приведенный выше код, чтобы упростить пример. См. раздел ASP.NET MVC Джона Аттена: сохранение закрытых параметров из системы управления версиями.

  6. Реализация передачи данных поставщику SMS

    SmsService Настройте класс в файле App_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. Щелкните идентификатор пользователя, который активирует Index метод действия в Manage контроллере.

    Изображение зарегистрированной учетной записи, вошедшего в приложение

  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);
}

Метод Index действия в Manage контроллере задает сообщение о состоянии на основе предыдущего действия и предоставляет ссылки для изменения локального пароля или добавления локальной учетной записи. Метод Index также отображает состояние или номер телефона 2FA, внешние имена входа, включенную двухфакторную проверку подлинности и метод 2FA для этого браузера (описано далее). Если щелкнуть идентификатор пользователя (адрес электронной почты) в строке заголовка, сообщение не передается. При щелчке по ссылке Номер телефона : удалить передается 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 настроена, маркер отправляется в виде строки "Ваш код безопасности является <токеном>". Метод 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 проверяет опубликованный код безопасности. Если код правильный, номер телефона добавляется в PhoneNumber поле AspNetUsers таблицы. Если этот вызов выполнен успешно, 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 задает, сохраняется ли сеанс проверки подлинности в нескольких запросах.

При изменении профиля безопасности создается новая метка безопасности и сохраняется в SecurityStamp поле таблицы AspNetUsers . Обратите внимание, что SecurityStamp поле отличается от файла cookie безопасности. Файл cookie безопасности не хранится в AspNetUsers таблице (или где-либо еще в базе данных удостоверений). Маркер cookie безопасности самозаверяется с помощью DPAPI и создается с UserId, SecurityStamp информацией о времени окончания срока действия и .

ПО промежуточного слоя для файлов cookie проверяет файл cookie при каждом запросе. Метод SecurityStampValidator в классе попадает в Startup базу данных и периодически проверяет метку безопасности, как указано в validateInterval. Это происходит каждые 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) и выйти из системы через 5 секунд при сбое SecurityStampValidator.OnValidateIdentity метода. Удалите строку возврата в методе SignInAsync , внесите другое изменение профиля безопасности, и вы не будете выйдут из системы. Метод SignInAsync создает новый файл cookie безопасности.

Включение двухфакторной проверки подлинности

В примере приложения необходимо использовать пользовательский интерфейс для включения двухфакторной проверки подлинности (2FA). Чтобы включить 2FA, щелкните свой идентификатор пользователя (псевдоним электронной почты) на панели навигации. Изображение U I для включения двухфакторной проверки подлинности
Щелкните Включить 2FA.Изображение после щелчка пользовательского идентификатора, показывающее ссылку включения двухфакторной проверки подлинности Выйдите из системы, а затем снова войдите в систему. Если вы включили электронную почту (см. мой предыдущий учебник), вы можете выбрать SMS или электронную почту для 2FA.Изображение с параметрами отправки проверки Отобразится страница Проверка кода, где можно ввести код (из SMS или электронной почты).Изображение кодовой страницы проверки Если щелкнуть поле Запомнить этот браузер проверка, вам не потребуется использовать 2FA для входа с помощью этого компьютера и браузера. Включение 2FA и нажатие кнопки Запомнить этот браузер обеспечит надежную защиту 2FA от злоумышленников, пытающихся получить доступ к вашей учетной записи, если у них нет доступа к вашему компьютеру. Это можно сделать на любом частном компьютере, который вы регулярно используете. Установив параметр Запомнить этот браузер, вы получаете дополнительную защиту двухфаковых параметров на компьютерах, которые вы не используете регулярно, и получаете удобство, не проходя через 2FA на собственных компьютерах.

Регистрация поставщика двухфакторной проверки подлинности

При создании проекта MVC файл IdentityConfig.cs содержит следующий код для регистрации поставщика двухфакторной проверки подлинности:

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

Метод AddPhoneNumber действия в контроллере Manage создает маркер безопасности и отправляет его на указанный вами номер телефона.

[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 метод действия, где можно ввести код для регистрации SMS для 2FA. 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 что должен вызываться , так как включение 2FA является изменением профиля безопасности. Если 2FA включена, пользователю потребуется использовать 2FA для входа, используя зарегистрированные подходы к 2FA (SMS и электронная почта в примере).

Вы можете добавить дополнительные поставщики 2FA, такие как генераторы QR-кода, или написать собственные.

Примечание

Коды 2FA создаются с помощью алгоритма одноразового пароля на основе времени , а коды действительны в течение шести минут. Если вы введете код более шести минут, вы получите сообщение об ошибке Недопустимый код.

Объединение учетных записей для входа в социальных сетях и локальных учетных записей

Вы можете объединить локальные учетные записи и учетные записи социальных параметров, щелкнув ссылку электронной почты. В следующей последовательности сначала создается локальноеRickAndMSFT@gmail.com имя входа, но вы можете сначала создать учетную запись как социальный вход, а затем добавить локальное имя входа.

Изображение: ссылка на электронную почту

Щелкните ссылку Управление . Обратите внимание на 0 внешних (учетных записей социальных параметров), связанных с этой учетной записью.

Изображение, отображающее следующую страницу и выбор элемента управления

Щелкните ссылку на другую службу входа и примите запросы приложения. Две учетные записи были объединены, и вы сможете войти в систему с помощью любой из них. Вы можете захотеть, чтобы пользователи добавляли локальные учетные записи в случае, если служба проверки подлинности в социальных сетях не работает или, скорее всего, они потеряли доступ к своей учетной записи социальной сети.

На следующем изображении Tom — это вход в социальную сеть (который можно увидеть на странице Внешние имена входа: 1 ).

Изображение, показывающее внешние имена входа и расположение выбора пароля

Щелкнув Выбрать пароль , вы можете добавить локальный вход, связанный с той же учетной записью.

Изображение страницы выбора пароля

Блокировка учетной записи от атак методом подбора

Вы можете защитить учетные записи в приложении от атак по словарю, включив блокировку пользователей. Следующий код в методе ApplicationUserManager Create настраивает блокировку:

// Configure user lockout defaults
manager.UserLockoutEnabledByDefault = true;
manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
manager.MaxFailedAccessAttemptsBeforeLockout = 5;

Приведенный выше код включает блокировку только для двухфакторной проверки подлинности. Хотя вы можете включить блокировку для имен входа, изменив shouldLockout значение true в Login методе контроллера учетной записи, рекомендуется не включать блокировку для имен входа, так как это делает учетную запись уязвимой для атак на вход 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, возвращается SignInStatus.Success , SignInManager и ей не придется проходить через 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, и если достигнуто максимальное количество попыток, пользователь блокируется на пять минут (вы можете задать время блокировки с помощью DefaultAccountLockoutTimeSpan).

Дополнительные ресурсы