Autenticazione a due fattori tramite SMS e posta elettronica con ASP.NET Identity

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

Questa esercitazione illustra come configurare l'autenticazione a due fattori (2FA) usando SMS e posta elettronica.

Questo articolo è stato scritto da Rick Anderson (@RickAndMSFT), Pranav Rastogi (@rustd), Hao Kung e Suhas Joshi. L'esempio NuGet è stato scritto principalmente da Hao Kung.

Questo argomento illustra quanto segue:

Compilazione dell'esempio identity

In questa sezione si userà NuGet per scaricare un esempio che verrà usato. Iniziare installando ed eseguendo Visual Studio Express 2013 per Web o Visual Studio 2013. Installare Visual Studio 2013 Update 2 o versione successiva.

Nota

Avviso: per completare questa esercitazione è necessario installare Visual Studio 2013 Update 2 .

  1. Creare un nuovo progetto Web di ASP.NET vuoto .

  2. Nella console di Gestione pacchetti immettere i comandi seguenti:

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

    In questa esercitazione si userà SendGrid per inviare messaggi di posta elettronica e Twilio o ASPSMS per il testo sms. Il Identity.Samples pacchetto installa il codice con cui verrà lavorato.

  3. Impostare il progetto per l'uso di SSL.

  4. Facoltativo: seguire le istruzioni riportate nell'esercitazione di conferma Email per collegare SendGrid e quindi eseguire l'app e registrare un account di posta elettronica.

  5. Opzionale: Rimuovere il codice di conferma del collegamento di posta elettronica demo dall'esempio (Il ViewBag.Link codice nel controller dell'account. Vedere i metodi di azione e ForgotPasswordConfirmation le visualizzazioni DisplayEmail razor .

  6. Opzionale: Rimuovere il ViewBag.Status codice dai controller Di gestione e account e dalle visualizzazioni\Account\VerifyCode.cshtml e Views\Manage\VerifyPhoneNumber.cshtml razor. In alternativa, è possibile mantenere la visualizzazione per testare il funzionamento locale di ViewBag.Status questa app senza dover collegare e inviare messaggi di posta elettronica e SMS.

Nota

Avviso: se si modifica una delle impostazioni di sicurezza in questo esempio, le app di produzione dovranno eseguire un controllo di sicurezza che chiama in modo esplicito le modifiche apportate.

Configurare SMS per l'autenticazione a due fattori

Questa esercitazione fornisce istruzioni per l'uso di Twilio o ASPSMS, ma è possibile usare qualsiasi altro provider SMS.

  1. Creazione di un account utente con un provider SMS

    Creare un account Twilio o un account ASPSMS .

  2. Installazione di pacchetti aggiuntivi o aggiunta di riferimenti al servizio

    Twilio:
    Nella Console di Gestione pacchetti immettere il comando seguente:
    Install-Package Twilio

    ASPSMS:
    È necessario aggiungere il riferimento al servizio seguente:

    Immagine della finestra di riferimento del servizio aggiunta

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

    Spazio dei nomi:
    ASPSMSX2

  3. Comprendere le credenziali utente del provider SMS

    Twilio:
    Nella scheda Dashboard dell'account Twilio copiare il SID account e il token di autenticazione.

    ASPSMS:
    Dalle impostazioni dell'account passare a Userkey e copiarlo insieme alla password self-defined.

    In seguito verranno archiviati questi valori nelle variabili SMSAccountIdentification e SMSAccountPassword .

  4. Specifica di SenderID/Originator

    Twilio:
    Nella scheda Numeri copiare il numero di telefono Twilio.

    ASPSMS:
    Nel menu Sblocca origini sbloccare uno o più originatori o scegliere un originatore alfanumerico (non supportato da tutte le reti).

    In seguito verrà archiviato questo valore nella variabile SMSAccountFrom .

  5. Trasferimento delle credenziali del provider SMS nell'app

    Rendere disponibili le credenziali e il numero di telefono del mittente per l'app:

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

    Avviso

    Sicurezza: non archiviare mai i dati sensibili nel codice sorgente. L'account e le credenziali vengono aggiunti al codice precedente per mantenere l'esempio semplice. Vedere La ASP.NET MVC di Jon Atten: Mantenere le impostazioni private fuori dal controllo del codice sorgente.

  6. Implementazione del trasferimento dei dati al provider SMS

    Configurare la SmsService classe nel file App_Start\IdentityConfig.cs .

    A seconda del provider SMS usato, attivare la sezione Twilio o 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. Eseguire l'app e accedere con l'account registrato in precedenza.

  8. Fare clic sull'ID utente, che attiva il Index metodo di azione nel Manage controller.

    Immagine dell'account registrato connesso all'app

  9. Fare clic su Aggiungi.

    Immagine del collegamento aggiungi numero di telefono

  10. In pochi secondi si riceverà un messaggio di testo con il codice di verifica. Immetterlo e premere Invia.

    Immagine che mostra la voce del codice di verifica del telefono

  11. La visualizzazione Gestisci mostra che è stato aggiunto il numero di telefono.

    Immagine della finestra di visualizzazione di gestione che mostra il numero di telefono

Esaminare il codice

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

Il Index metodo di azione nel Manage controller imposta il messaggio di stato in base all'azione precedente e fornisce collegamenti per modificare la password locale o aggiungere un account locale. Il Index metodo visualizza anche lo stato o il numero di telefono 2FA, gli account di accesso esterni, 2FA abilitati e ricordano il metodo 2FA per questo browser(spiegato più avanti). Facendo clic sull'ID utente (posta elettronica) nella barra del titolo non viene passato un messaggio. Facendo clic sul numero di telefono : rimuovere il collegamento passa Message=RemovePhoneSuccess come stringa di query.

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

[Immagine del numero di telefono rimosso]

Il AddPhoneNumber metodo azione visualizza una finestra di dialogo per immettere un numero di telefono che può ricevere messaggi SMS.

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

Immagine della finestra di dialogo Aggiungi numero di telefono

Facendo clic sul pulsante Invia codice di verifica viene inviato il numero di telefono al metodo di azione 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 });
}

Il GenerateChangePhoneNumberTokenAsync metodo genera il token di sicurezza che verrà impostato nel messaggio SMS. Se il servizio SMS è stato configurato, il token viene inviato come stringa "Il codice di sicurezza è <token>". Il SmsService.SendAsync metodo da chiamare in modo asincrono, quindi l'app viene reindirizzata al VerifyPhoneNumber metodo action (che visualizza la finestra di dialogo seguente), dove è possibile immettere il codice di verifica.

Immagine della finestra di dialogo Del metodo di azione del numero di telefono

Dopo aver immesso il codice e fare clic su Invia, il codice viene pubblicato nel metodo di azione 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);
}

Il ChangePhoneNumberAsync metodo controlla il codice di sicurezza pubblicato. Se il codice è corretto, il numero di telefono viene aggiunto al PhoneNumber campo della AspNetUsers tabella. Se la chiamata ha esito positivo, viene chiamato il SignInAsync metodo:

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

Il isPersistent parametro imposta se la sessione di autenticazione viene mantenuta in più richieste.

Quando si modifica il profilo di sicurezza, viene generato un nuovo timbro di sicurezza e archiviato nel SecurityStamp campo della tabella AspNetUsers . Nota, il SecurityStamp campo è diverso dal cookie di sicurezza. Il cookie di sicurezza non viene archiviato nella AspNetUsers tabella (o altrove nel database Identity). Il token del cookie di sicurezza è autofirmato usando DPAPI e viene creato con le informazioni sull'ora di UserId, SecurityStamp scadenza e.

Il middleware del cookie controlla il cookie in ogni richiesta. Il SecurityStampValidator metodo nella Startup classe raggiunge il database e controlla periodicamente il timbro di sicurezza, come specificato con .validateInterval Questo avviene solo ogni 30 minuti (nell'esempio) a meno che non si modifica il profilo di sicurezza. L'intervallo di 30 minuti è stato scelto per ridurre al minimo i viaggi al database.

Il SignInAsync metodo deve essere chiamato quando viene apportata una modifica al profilo di sicurezza. Quando il profilo di sicurezza viene modificato, il database aggiorna il SecurityStamp campo e senza chiamare il SignInAsync metodo in cui si rimane connessi solo fino alla successiva volta che la pipeline OWIN raggiunge il database (l'oggetto validateInterval). È possibile testare questa operazione modificando il SignInAsync metodo per restituire immediatamente e impostando la proprietà cookie validateInterval da 30 minuti a 5 secondi:

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

Con le modifiche apportate al codice precedente, è possibile modificare il profilo di sicurezza(ad esempio modificando lo stato di Two Factor Enabled) e si verrà disconnessi in 5 secondi quando il SecurityStampValidator.OnValidateIdentity metodo ha esito negativo. Rimuovere la riga restituita nel SignInAsync metodo, apportare un'altra modifica del profilo di sicurezza e non verrà disconnessa. Il SignInAsync metodo genera un nuovo cookie di sicurezza.

Abilitare l'autenticazione a due fattori

Nell'app di esempio è necessario usare l'interfaccia utente per abilitare l'autenticazione a due fattori (2FA). Per abilitare 2FA, fare clic sull'ID utente (alias di posta elettronica) nella barra di spostamento. Immagine di U I per abilitare l'autenticazione a due fattori
Fare clic su abilita 2FA.Immagine dopo aver fatto clic sull'utente D che mostra il collegamento abilita l'autenticazione a due fattori Disconnettersi, quindi accedere nuovamente. Se il messaggio di posta elettronica è stato abilitato (vedere l'esercitazione precedente), è possibile selezionare l'SMS o il messaggio di posta elettronica per 2FA.Immagine che visualizza le opzioni di invio di verifica La pagina Verifica codice viene visualizzata in cui è possibile immettere il codice (da SMS o posta elettronica).Immagine della tabella codici di verifica Facendo clic sulla casella di controllo Ricorda questo browser , è necessario usare 2FA per accedere a tale computer e browser. L'abilitazione di 2FA e fare clic su Ricorda questo browser offre una protezione 2FA avanzata dagli utenti malintenzionati che tentano di accedere all'account, purché non abbiano accesso al computer. È possibile eseguire questa operazione in qualsiasi computer privato usato regolarmente. Impostando Ricorda questo browser, si ottiene la sicurezza aggiunta di 2FA dai computer che non si usano regolarmente e si ottiene la praticità su non dover passare attraverso 2FA nei propri computer.

Come registrare un provider di autenticazione a due fattori

Quando si crea un nuovo progetto MVC, il file IdentityConfig.cs contiene il codice seguente per registrare un provider di autenticazione a due fattori:

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

Aggiungere un numero di telefono per 2FA

Il AddPhoneNumber metodo di azione nel Manage controller genera un token di sicurezza e lo invia al numero di telefono specificato.

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

Dopo aver inviato il token, reindirizza al VerifyPhoneNumber metodo azione, dove è possibile immettere il codice per registrare SMS per 2FA. SMS 2FA non viene usato finché non è stato verificato il numero di telefono.

Abilitazione di 2FA

Il EnableTFA metodo action abilita 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");
}

Nota che SignInAsync deve essere chiamato perché enable 2FA è una modifica al profilo di sicurezza. Quando la funzionalità 2FA è abilitata, l'utente dovrà usare 2FA per accedere, usando gli approcci 2FA registrati (SMS e posta elettronica nell'esempio).

È possibile aggiungere altri provider 2FA, ad esempio generatori di codice a matrice o è possibile scrivere il proprio.

Nota

I codici 2FA vengono generati usando algoritmo password one-time basato su tempo e i codici sono validi per sei minuti. Se si richiedono più di sei minuti per immettere il codice, verrà visualizzato un messaggio di errore di codice non valido.

Combinare account di accesso social e locali

È possibile combinare account locali e social facendo clic sul collegamento di posta elettronica. Nella sequenza seguente vieneRickAndMSFT@gmail.com prima creato come account di accesso locale, ma è possibile creare l'account come account di social log in primo luogo, quindi aggiungere un account di accesso locale.

Immagine che seleziona il collegamento di posta elettronica

Fare clic sul collegamento Gestisci . Si notino i 0 account esterni (account di accesso social) associati a questo account.

Immagine che visualizza la pagina successiva e seleziona gestione

Fare clic sul collegamento a un altro servizio di accesso e accettare le richieste dell'app. I due account sono stati combinati, sarà possibile accedere con entrambi gli account. È possibile che gli utenti vogliano aggiungere account locali nel caso in cui il servizio di autenticazione dei social log sia inattivo o più probabilmente hanno perso l'accesso al proprio account social.

Nell'immagine seguente Tom è un accesso social (che è possibile visualizzare dagli account di accesso esterni: 1 visualizzato nella pagina).

Immagine che mostra gli account di accesso esterni e la posizione di selezione di una password

Facendo clic su Seleziona una password è possibile aggiungere un accesso locale associato allo stesso account.

Immagine della selezione di una pagina password

Blocco dell'account da attacchi di forza bruta

È possibile proteggere gli account nell'app da attacchi di dizionario abilitando il blocco utente. Il codice seguente nel metodo configura il ApplicationUserManager Create blocco:

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

Il codice precedente abilita il blocco solo per l'autenticazione a due fattori. Anche se è possibile abilitare il blocco per gli account di accesso modificando shouldLockout in true nel Login metodo del controller di account, è consigliabile non abilitare il blocco per gli account di accesso perché rende l'account soggetto agli attacchi di accesso DOS . Nel codice di esempio il blocco è disabilitato per l'account amministratore creato nel ApplicationDbInitializer Seed metodo:

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

Richiedere a un utente di avere un account di posta elettronica convalidato

Il codice seguente richiede a un utente di avere un account di posta elettronica convalidato prima di poter accedere:

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

Come SignInManager controlla il requisito 2FA

Sia l'accesso locale che l'accesso social per verificare se 2FA è abilitato. Se 2FA è abilitato, il SignInManager metodo di accesso restituisce SignInStatus.RequiresVerificatione l'utente verrà reindirizzato al SendCode metodo di azione, dove dovranno immettere il codice per completare la sequenza di accesso. Se l'utente ha RememberMe è impostato sul cookie locale degli utenti, il SignInManager restituirà SignInStatus.Success e non dovrà passare attraverso 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 });
    }
}

Il codice seguente mostra il SendCode metodo action. Viene creato un oggetto SelectListItem con tutti i metodi 2FA abilitati per l'utente. SelectListItem viene passato all'helper DropDownListFor, che consente all'utente di selezionare l'approccio 2FA (in genere e-mail 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 });
}

Dopo che l'utente pubblica l'approccio 2FA, viene chiamato il HTTP POST SendCode metodo action, SignInManager invia il codice 2FA e l'utente viene reindirizzato al VerifyCode metodo azione in cui possono immettere il codice per completare l'accesso.

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

Blocco 2FA

Anche se è possibile impostare il blocco dell'account in caso di errori di tentativi di password di accesso, questo approccio rende l'accesso soggetto ai blocchi DOS . È consigliabile usare il blocco dell'account solo con 2FA. ApplicationUserManager Quando viene creato, il codice di esempio imposta il blocco 2FA e MaxFailedAccessAttemptsBeforeLockout su cinque. Dopo che un utente accede (tramite account locale o account social), ogni tentativo non riuscito a 2FA viene archiviato e, se il massimo tentativo viene raggiunto, l'utente viene bloccato per cinque minuti (è possibile impostare il tempo di blocco con DefaultAccountLockoutTimeSpan).

Risorse aggiuntive