Compartilhar via


Autenticação de dois fatores usando SMS e email com o ASP.NET Identity

por Hao Kung, Pranav Rastogi, Rick Anderson, Suhas Joshi

Este tutorial mostrará como configurar a 2FA (autenticação de dois fatores) usando SMS e email.

Este artigo foi escrito por Rick Anderson (@RickAndMSFT), Pranav Rastogi (@rustd), Hao Kung e Suhas Joshi. A amostra do NuGet foi escrita principalmente por Hao Kung.

Este tópico aborda o seguinte:

Compilando o exemplo de identidade

Nesta seção, você usará o NuGet para baixar um exemplo com o qual trabalharemos. Comece instalando e executando o Visual Studio Express 2013 para Web ou Visual Studio 2013. Instale o Visual Studio 2013 Atualização 2 ou superior.

Observação

Aviso: você deve instalar o Visual Studio 2013 Atualização 2 para concluir este tutorial.

  1. Crie um novo projeto Web ASP.NET vazio .

  2. No Console do Gerenciador de Pacotes, insira os seguintes comandos:

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

    Neste tutorial, usaremos SendGrid para enviar email e Twilio ou ASPSMS para sms. O Identity.Samples pacote instala o código com o qual trabalharemos.

  3. Defina o projeto para usar SSL.

  4. Opcional: siga as instruções no tutorial de confirmação do meu Email para conectar o SendGrid e, em seguida, executar o aplicativo e registrar uma conta de email.

  5. Opcional: Remova o código de confirmação do link de email de demonstração do exemplo (o ViewBag.Link código no controlador de conta. Consulte os DisplayEmail métodos de ação e ForgotPasswordConfirmation e as exibições razor ).

  6. Opcional: Remova o ViewBag.Status código dos controladores Gerenciar e Conta e das exibições razor Views\Account\VerifyCode.cshtml e Views\Manage\VerifyPhoneNumber.cshtml . Como alternativa, você pode manter a ViewBag.Status exibição para testar como esse aplicativo funciona localmente sem precisar conectar e enviar emails e mensagens SMS.

Observação

Aviso: se você alterar qualquer uma das configurações de segurança neste exemplo, os aplicativos de produção precisarão passar por uma auditoria de segurança que chame explicitamente as alterações feitas.

Configurar o SMS para autenticação de dois fatores

Este tutorial fornece instruções para usar o Twilio ou o ASPSMS, mas você pode usar qualquer outro provedor de SMS.

  1. Criando uma conta de usuário com um provedor de SMS

    Crie uma conta do Twilio ou do ASPSMS .

  2. Instalar pacotes adicionais ou adicionar referências de serviço

    Twilio:
    No Console do Gerenciador de Pacotes, digite o seguinte comando:
    Install-Package Twilio

    ASPSMS:
    A seguinte referência de serviço precisa ser adicionada:

    Imagem da janela adicionar referência de serviço

    Endereço:
    https://webservice.aspsms.com/aspsmsx2.asmx?WSDL

    Namespace:
    ASPSMSX2

  3. Descobrir as credenciais do usuário do provedor de SMS

    Twilio:
    Na guia Painel da sua conta do Twilio, copie o SID da conta e o token de autenticação.

    ASPSMS:
    Nas configurações da sua conta, navegue até Userkey e copie-a junto com sua Senha autodefinida.

    Posteriormente, armazenaremos esses valores nas variáveis SMSAccountIdentification e SMSAccountPassword .

  4. Especificando SenderID/Originator

    Twilio:
    Na guia Números , copie o número de telefone do Twilio.

    ASPSMS:
    No Menu Desbloquear Originadores , desbloqueie um ou mais Originadores ou escolha um Originador alfanumérico (sem suporte de todas as redes).

    Posteriormente, armazenaremos esse valor na variável SMSAccountFrom .

  5. Transferindo credenciais do provedor de SMS para o aplicativo

    Disponibilize as credenciais e o número de telefone do remetente para o aplicativo:

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

    Aviso

    Segurança – Nunca armazene dados confidenciais em seu código-fonte. A conta e as credenciais são adicionadas ao código acima para manter o exemplo simples. Confira o ASP.NET MVC de Jon Atten : Manter configurações privadas fora do controle do código-fonte.

  6. Implementação da transferência de dados para o provedor de SMS

    Configure a SmsService classe no arquivo App_Start\IdentityConfig.cs .

    Dependendo do provedor de SMS usado, ative a seção Twilio ou 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. Execute o aplicativo e faça logon com a conta que você registrou anteriormente.

  8. Clique em sua ID de Usuário, que ativa o método de Index ação no Manage controlador.

    Imagem da conta registrada registrada registrada no aplicativo

  9. Clique em Adicionar.

    Imagem do link Adicionar número de telefone

  10. Em alguns segundos, você receberá uma mensagem de texto com o código de verificação. Insira-o e pressione Enviar.

    Imagem mostrando a entrada do código de verificação do telefone

  11. O modo de exibição Gerenciar mostra que o número de telefone foi adicionado.

    Imagem da janela gerenciar exibição mostrando o número de telefone

Examinar o código

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

O Index método de ação no Manage controlador define a mensagem status com base em sua ação anterior e fornece links para alterar sua senha local ou adicionar uma conta local. O Index método também exibe o estado ou seu número de telefone 2FA, logons externos, 2FA habilitado e lembre-se do método 2FA para este navegador (explicado posteriormente). Clicar em sua ID de usuário (email) na barra de título não passa uma mensagem. Clicando no número de telefone : remover passagens Message=RemovePhoneSuccess de link como uma cadeia de caracteres de consulta.

https://localhost:44300/Manage?Message=RemovePhoneSuccess

[Imagem do número de telefone removida]

O AddPhoneNumber método de ação exibe uma caixa de diálogo para inserir um número de telefone que pode receber mensagens SMS.

// GET: /Account/AddPhoneNumber
public ActionResult AddPhoneNumber()
{
   return View();
}

Imagem da caixa de diálogo adicionar ação de número de telefone

Clicar no botão Enviar código de verificação posta o número de telefone no método de ação 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 });
}

O GenerateChangePhoneNumberTokenAsync método gera o token de segurança que será definido na mensagem SMS. Se o serviço de SMS tiver sido configurado, o token será enviado como a cadeia de caracteres "Seu código de segurança é <token>". O SmsService.SendAsync método para é chamado de forma assíncrona e, em seguida, o aplicativo é redirecionado para o VerifyPhoneNumber método de ação (que exibe a caixa de diálogo a seguir), em que você pode inserir o código de verificação.

Imagem da caixa de diálogo verificar método de ação de número de telefone

Depois de inserir o código e clicar em enviar, o código será postado no método de ação 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);
}

O ChangePhoneNumberAsync método verifica o código de segurança postado. Se o código estiver correto, o número de telefone será adicionado ao PhoneNumber campo da AspNetUsers tabela. Se essa chamada for bem-sucedida, o SignInAsync método será chamado:

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

O isPersistent parâmetro define se a sessão de autenticação é mantida em várias solicitações.

Quando você altera seu perfil de segurança, um novo carimbo de segurança é gerado e armazenado no SecurityStamp campo da tabela AspNetUsers . Observe que o SecurityStamp campo é diferente do cookie de segurança. O cookie de segurança não é armazenado na AspNetUsers tabela (ou em qualquer outro lugar no Banco de Dados de Identidade). O token de cookie de segurança é autoassinado usando DPAPI e é criado com as UserId, SecurityStamp informações de tempo de expiração e .

O middleware de cookie verifica o cookie em cada solicitação. O SecurityStampValidator método na Startup classe atinge o BD e verifica o carimbo de segurança periodicamente, conforme especificado com o validateInterval. Isso só acontece a cada 30 minutos (em nosso exemplo), a menos que você altere seu perfil de segurança. O intervalo de 30 minutos foi escolhido para minimizar as viagens ao banco de dados.

O SignInAsync método precisa ser chamado quando qualquer alteração é feita no perfil de segurança. Quando o perfil de segurança for alterado, o banco de dados atualizará o SecurityStamp campo e, sem chamar o SignInAsync método, você permanecerá conectado somente até a próxima vez que o pipeline OWIN atingir o banco de dados (o validateInterval). Você pode testar isso alterando o SignInAsync método para retornar imediatamente e definindo a propriedade cookie validateInterval de 30 minutos para 5 segundos:

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

Com as alterações de código acima, você pode alterar seu perfil de segurança (por exemplo, alterando o estado de Dois Fatores Habilitados) e você será conectado em 5 segundos quando o SecurityStampValidator.OnValidateIdentity método falhar. Remova a linha de retorno no SignInAsync método , faça outra alteração de perfil de segurança e você não será desconectado. O SignInAsync método gera um novo cookie de segurança.

Habilitar a autenticação de dois fatores

No aplicativo de exemplo, você precisa usar a interface do usuário para habilitar a 2FA (autenticação de dois fatores). Para habilitar a 2FA, clique em sua ID de usuário (alias de email) na barra de navegação. Imagem de IU para habilitar a autenticação de dois fatores
Clique em habilitar 2FA.Imagem depois de clicar na ID do usuário mostrando o link habilitar a autenticação de dois fatores Faça logoff e faça logon novamente. Se você habilitou o email (consulte meu tutorial anterior), selecione o SMS ou email para 2FA.Imagem exibindo opções de envio de verificação A página Verificar Código é exibida onde você pode inserir o código (por SMS ou email).Imagem da página verificar código Clicar na caixa Lembrar este navegador marcar isentará você de precisar usar a 2FA para fazer logon com esse computador e navegador. Habilitar a 2FA e clicar no navegador Lembrar deste navegador fornecerá uma forte proteção 2FA contra usuários mal-intencionados que tentam acessar sua conta, desde que não tenham acesso ao computador. Você pode fazer isso em qualquer computador privado usado regularmente. Ao definir Lembrar este navegador, você obtém a segurança adicional da 2FA de computadores que não usa regularmente e obtém a conveniência de não precisar passar pela 2FA em seus próprios computadores.

Como registrar um provedor de autenticação de dois fatores

Quando você cria um novo projeto MVC, o arquivo IdentityConfig.cs contém o seguinte código para registrar um provedor de autenticação de dois fatores:

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

Adicionar um número de telefone para 2FA

O AddPhoneNumber método de ação no Manage controlador gera um token de segurança e o envia para o número de telefone fornecido.

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

Depois de enviar o token, ele redireciona para o VerifyPhoneNumber método de ação, no qual você pode inserir o código para registrar o SMS para 2FA. SMS 2FA não é usado até que você tenha verificado o número de telefone.

Habilitando a 2FA

O EnableTFA método de ação habilita a 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");
}

Observe que o SignInAsync deve ser chamado porque habilitar 2FA é uma alteração no perfil de segurança. Quando a 2FA estiver habilitada, o usuário precisará usar a 2FA para fazer logon, usando as abordagens 2FA que registrou (SMS e email no exemplo).

Você pode adicionar mais provedores de 2FA, como geradores de código QR ou pode escrever seus próprios.

Observação

Os códigos 2FA são gerados usando o Algoritmo de Senha Única baseado em tempo e os códigos são válidos por seis minutos. Se você levar mais de seis minutos para inserir o código, receberá uma mensagem de erro de código inválida.

Combinar contas de logon sociais e locais

Você pode combinar contas locais e sociais clicando em seu link de email. Na sequênciaRickAndMSFT@gmail.com "" a seguir é criada primeiro como um logon local, mas você pode criar a conta como um logon social primeiro e, em seguida, adicionar um logon local.

Imagem selecionando o link de email

Clique no link Gerenciar . Observe as 0 externas (logons sociais) associadas a essa conta.

Imagem exibindo a próxima página e selecionando gerenciar

Clique no link para outro serviço de logon e aceite as solicitações do aplicativo. As duas contas foram combinadas, você poderá fazer logon com qualquer uma das contas. Talvez você queira que os usuários adicionem contas locais caso o serviço de autenticação de logon social esteja inativo ou provavelmente eles tenham perdido o acesso à conta social.

Na imagem a seguir, Tom é um logon social (que você pode ver nos Logons Externos: 1 mostrado na página).

Imagem mostrando logons externos e o local da escolha de uma senha

Clicar em Escolher uma senha permite adicionar um logon local associado à mesma conta.

Imagem de escolher uma página de senha

Bloqueio de conta de ataques de força bruta

Você pode proteger as contas em seu aplicativo contra ataques de dicionário habilitando o bloqueio do usuário. O código a seguir no método configura o ApplicationUserManager Create bloqueio:

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

O código acima habilita o bloqueio somente para autenticação de dois fatores. Embora você possa habilitar o bloqueio para logons alterando shouldLockout para true no Login método do controlador de conta, recomendamos que você não habilite o bloqueio para logons porque ele torna a conta suscetível a ataques de logon dos DOS . No código de exemplo, o bloqueio é desabilitado para a conta de administrador criada no ApplicationDbInitializer Seed método :

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

Exigir que um usuário tenha uma conta de email validada

O código a seguir exige que um usuário tenha uma conta de email validada antes de fazer logon:

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

Como SignInManager verifica o requisito de 2FA

O logon local e o logon social marcar para ver se a 2FA está habilitada. Se a 2FA estiver habilitada, o SignInManager método de logon retornará SignInStatus.RequiresVerificatione o usuário será redirecionado para o SendCode método de ação, no qual precisará inserir o código para concluir a sequência de logon. Se o usuário tiver RememberMe definido no cookie local dos usuários, o SignInManager retornará SignInStatus.Success e ele não precisará passar pela 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 });
    }
}

O código a seguir mostra o método de SendCode ação. Um SelectListItem é criado com todos os métodos 2FA habilitados para o usuário. O SelectListItem é passado para o auxiliar DropDownListFor , que permite que o usuário selecione a abordagem 2FA (normalmente email e 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 });
}

Depois que o usuário posta a abordagem 2FA, o HTTP POST SendCode método de ação é chamado, o SignInManager envia o código 2FA e o usuário é redirecionado para o VerifyCode método de ação em que pode inserir o código para concluir o logon.

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

Bloqueio 2FA

Embora você possa definir o bloqueio de conta em falhas de tentativa de senha de logon, essa abordagem torna seu logon suscetível a bloqueios dos DOS . Recomendamos que você use o bloqueio de conta somente com 2FA. Quando o ApplicationUserManager é criado, o código de exemplo define o bloqueio de 2FA e MaxFailedAccessAttemptsBeforeLockout para cinco. Depois que um usuário faz logon (por meio de conta local ou conta social), cada tentativa com falha na 2FA é armazenada e, se as tentativas máximas forem atingidas, o usuário será bloqueado por cinco minutos (você pode definir o tempo de bloqueio com DefaultAccountLockoutTimeSpan).

Recursos adicionais