Partager via


Authentification à deux facteurs par SMS et e-mail avec ASP.NET Identity

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

Ce tutoriel vous montre comment configurer l’authentification à deux facteurs (2FA) à l’aide d’un SMS et d’un e-mail.

Cet article a été écrit par Rick Anderson (@RickAndMSFT), Pranav Rastogi (@rustd), Hao Kung et Suhas Joshi. L’exemple NuGet a été écrit principalement par Hao Kung.

Cette rubrique couvre les sujets suivants :

Génération de l’exemple d’identité

Dans cette section, vous allez utiliser NuGet pour télécharger un exemple avec lequel nous allons travailler. Commencez par installer et exécuter Visual Studio Express 2013 pour le web ou Visual Studio 2013. Installez Visual Studio 2013 Update 2 ou version ultérieure.

Notes

Avertissement : Vous devez installer Visual Studio 2013 Update 2 pour suivre ce didacticiel.

  1. Créez un projet web ASP.NET vide .

  2. Dans la console du Gestionnaire de package, entrez les commandes suivantes :

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

    Dans ce tutoriel, nous allons utiliser SendGrid pour envoyer des messages électroniques et Twilio ou ASPSMS pour les SMS. Le Identity.Samples package installe le code avec lequel nous allons travailler.

  3. Définissez le projet pour utiliser SSL.

  4. Facultatif : suivez les instructions de mon didacticiel de confirmation Email pour raccorder SendGrid, puis exécutez l’application et inscrivez un compte de messagerie.

  5. Optionnel: Supprimez le code de confirmation du lien d’e-mail de démonstration de l’exemple (code ViewBag.Link dans le contrôleur de compte. Consultez les méthodes d’action DisplayEmail et et ForgotPasswordConfirmation les vues razor ).

  6. Optionnel: Supprimez le ViewBag.Status code des contrôleurs Gérer et Compte et des vues razor Views\Account\VerifyCode.cshtml et Views\Manage\VerifyPhoneNumber.cshtml . Vous pouvez également conserver l’affichage pour tester le ViewBag.Status fonctionnement de cette application localement sans avoir à vous connecter et à envoyer des e-mails et des SMS.

Notes

Avertissement : si vous modifiez l’un des paramètres de sécurité de cet exemple, les applications de production doivent subir un audit de sécurité qui appelle explicitement les modifications apportées.

Configurer SMS pour l’authentification à deux facteurs

Ce tutoriel fournit des instructions pour utiliser Twilio ou ASPSMS, mais vous pouvez utiliser n’importe quel autre fournisseur SMS.

  1. Création d’un compte d’utilisateur avec un fournisseur SMS

    Créez un compte Twilio ou ASPSMS .

  2. Installation de packages supplémentaires ou ajout de références de service

    Twilio:
    Dans la Console du gestionnaire de package, entrez la commande suivante :
    Install-Package Twilio

    ASPSMS :
    La référence de service suivante doit être ajoutée :

    Image de la fenêtre Ajouter une référence de service

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

    Espace de noms :
    ASPSMSX2

  3. Identification des informations d’identification de l’utilisateur du fournisseur SMS

    Twilio:
    Sous l’onglet Tableau de bord de votre compte Twilio, copiez le SID de compte et le jeton d’authentification.

    ASPSMS :
    Dans les paramètres de votre compte, accédez à Userkey et copiez-le avec votre mot de passe autodéfini.

    Nous stockerons plus tard ces valeurs dans les variables SMSAccountIdentification et SMSAccountPassword .

  4. Spécification de SenderID/Originator

    Twilio:
    Sous l’onglet Nombres , copiez votre numéro de téléphone Twilio.

    ASPSMS :
    Dans le menu Déverrouiller les originateurs , déverrouillez un ou plusieurs originateurs ou choisissez un originateur alphanumérique (non pris en charge par tous les réseaux).

    Nous stockerons plus tard cette valeur dans la variable SMSAccountFrom .

  5. Transfert des informations d’identification du fournisseur SMS dans l’application

    Mettent les informations d’identification et le numéro de téléphone de l’expéditeur à la disposition de l’application :

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

    Avertissement

    Sécurité : ne stockez jamais de données sensibles dans votre code source. Le compte et les informations d’identification sont ajoutés au code ci-dessus pour simplifier l’exemple. Consultez le ASP.NET MVC de Jon Atten : Conserver les paramètres privés hors contrôle de code source.

  6. Implémentation du transfert de données vers le fournisseur SMS

    Configurez la SmsService classe dans le fichier App_Start\IdentityConfig.cs .

    Selon le fournisseur SMS utilisé, activez la section 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. Exécutez l’application et connectez-vous avec le compte que vous avez précédemment inscrit.

  8. Cliquez sur votre ID d’utilisateur, ce qui active la méthode d’action dans le IndexManage contrôleur.

    Image du compte inscrit connecté à l’application

  9. Cliquez sur Ajouter.

    Image du lien Ajouter un numéro de téléphone

  10. En quelques secondes, vous recevrez un sms avec le code de vérification. Entrez-le et appuyez sur Envoyer.

    Image montrant l’entrée de code de vérification par téléphone

  11. La vue Gérer indique que votre numéro de téléphone a été ajouté.

    Image de la fenêtre gérer l’affichage montrant le numéro de téléphone

Examiner le code

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

La Index méthode d’action dans Manage le contrôleur définit le message status en fonction de votre action précédente et fournit des liens permettant de modifier votre mot de passe local ou d’ajouter un compte local. La Index méthode affiche également l’état ou votre numéro de téléphone 2FA, les connexions externes, la 2FA activée et mémoriser la méthode 2FA pour ce navigateur (expliqué plus loin). Le fait de cliquer sur votre ID utilisateur (e-mail) dans la barre de titre ne transmet pas de message. Cliquez sur le lien Numéro de téléphone : supprimer passe Message=RemovePhoneSuccess en tant que chaîne de requête.

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

[Image du numéro de téléphone supprimé]

La AddPhoneNumber méthode d’action affiche une boîte de dialogue pour entrer un numéro de téléphone pouvant recevoir des SMS.

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

Image de la boîte de dialogue Ajouter une action de numéro de téléphone

En cliquant sur le bouton Envoyer le code de vérification , le numéro de téléphone est affiché dans la méthode d’action 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 });
}

La GenerateChangePhoneNumberTokenAsync méthode génère le jeton de sécurité qui sera défini dans le message SMS. Si le service SMS a été configuré, le jeton est envoyé en tant que chaîne « Votre code de sécurité est <jeton> ». La SmsService.SendAsync méthode à est appelée de manière asynchrone, puis l’application est redirigée vers la VerifyPhoneNumber méthode d’action (qui affiche la boîte de dialogue suivante), où vous pouvez entrer le code de vérification.

Image de la boîte de dialogue Vérifier la méthode d’action du numéro de téléphone

Une fois que vous avez entré le code et que vous avez cliqué sur Envoyer, le code est publié dans la méthode d’action 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);
}

La ChangePhoneNumberAsync méthode vérifie le code de sécurité publié. Si le code est correct, le numéro de téléphone est ajouté au PhoneNumber champ de la AspNetUsers table. Si cet appel réussit, la SignInAsync méthode est appelée :

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

Le isPersistent paramètre définit si la session d’authentification est conservée sur plusieurs demandes.

Lorsque vous modifiez votre profil de sécurité, un nouveau tampon de sécurité est généré et stocké dans le SecurityStamp champ de la table AspNetUsers . Notez que le SecurityStamp champ est différent du cookie de sécurité. Le cookie de sécurité n’est pas stocké dans la AspNetUsers table (ou n’importe où ailleurs dans la base de données d’identité). Le jeton de cookie de sécurité est auto-signé à l’aide de DPAPI et est créé avec les UserId, SecurityStamp informations de délai d’expiration et .

L’intergiciel de cookie vérifie le cookie à chaque demande. La SecurityStampValidator méthode de la Startup classe atteint la base de données et vérifie régulièrement l’empreinte de sécurité, comme spécifié avec le validateInterval. Cela ne se produit que toutes les 30 minutes (dans notre exemple), sauf si vous modifiez votre profil de sécurité. L’intervalle de 30 minutes a été choisi pour réduire les déplacements vers la base de données.

La SignInAsync méthode doit être appelée quand une modification est apportée au profil de sécurité. Lorsque le profil de sécurité change, la base de données met à jour le SecurityStamp champ, et sans appeler la SignInAsync méthode, vous restez connecté uniquement jusqu’à la prochaine fois que le pipeline OWIN atteint la base de données (le validateInterval). Vous pouvez tester cela en modifiant la SignInAsync méthode pour qu’elle retourne immédiatement et en définissant la propriété de cookie validateInterval de 30 minutes à 5 secondes :

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

Avec les modifications de code ci-dessus, vous pouvez modifier votre profil de sécurité (par exemple, en modifiant l’état de Two Factor Enabled) et vous serez déconnecté en 5 secondes lorsque la SecurityStampValidator.OnValidateIdentity méthode échoue. Supprimez la ligne de retour dans la SignInAsync méthode, modifiez un autre profil de sécurité et vous ne serez pas déconnecté. La SignInAsync méthode génère un nouveau cookie de sécurité.

Activer l’authentification à deux facteurs

Dans l’exemple d’application, vous devez utiliser l’interface utilisateur pour activer l’authentification à deux facteurs (2FA). Pour activer 2FA, cliquez sur votre ID d’utilisateur (alias d’e-mail) dans la barre de navigation. Image de l’U I pour activer l’authentification à deux facteurs
Cliquez sur Activer 2FA.Image après avoir cliqué sur l’id d’utilisateur montrant le lien activer l’authentification à deux facteurs Déconnectez-vous, puis reconnectez-vous. Si vous avez activé l’e-mail (voir mon tutoriel précédent), vous pouvez sélectionner le SMS ou l’e-mail pour 2FA.Image affichant les options d’envoi de vérification La page Vérifier le code s’affiche dans laquelle vous pouvez entrer le code (à partir d’un SMS ou d’un e-mail).Image de la page vérifier le code Si vous cliquez sur la zone Mémoriser ce navigateur case activée, vous n’aurez pas besoin d’utiliser 2FA pour vous connecter avec cet ordinateur et ce navigateur. L’activation de la 2FA et le fait de cliquer sur Mémoriser ce navigateur vous offre une protection 2FA forte contre les utilisateurs malveillants qui tentent d’accéder à votre compte, tant qu’ils n’ont pas accès à votre ordinateur. Vous pouvez le faire sur n’importe quelle machine privée que vous utilisez régulièrement. En définissant Mémoriser ce navigateur, vous bénéficiez de la sécurité supplémentaire de 2FA à partir d’ordinateurs que vous n’utilisez pas régulièrement, et vous obtenez la commodité de ne pas avoir à passer par 2FA sur vos propres ordinateurs.

Comment inscrire un fournisseur d’authentification à deux facteurs

Lorsque vous créez un projet MVC, le fichier IdentityConfig.cs contient le code suivant pour inscrire un fournisseur d’authentification à deux facteurs :

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

Ajouter un numéro de téléphone pour 2FA

La AddPhoneNumber méthode d’action dans le Manage contrôleur génère un jeton de sécurité et l’envoie au numéro de téléphone que vous avez fourni.

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

Après avoir envoyé le jeton, il redirige vers la VerifyPhoneNumber méthode d’action, où vous pouvez entrer le code pour inscrire des SMS pour 2FA. SMS 2FA n’est pas utilisé tant que vous n’avez pas vérifié le numéro de téléphone.

Activation de la 2FA

La EnableTFA méthode d’action active 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");
}

Notez que doit SignInAsync être appelé, car activer 2FA est une modification du profil de sécurité. Lorsque la 2FA est activée, l’utilisateur doit utiliser 2FA pour se connecter, à l’aide des approches 2FA qu’il a inscrites (SMS et e-mail dans l’exemple).

Vous pouvez ajouter d’autres fournisseurs 2FA tels que des générateurs de code QR ou écrire les vôtres.

Notes

Les codes 2FA sont générés à l’aide de l’algorithme de mot de passe à usage unique basé sur le temps et les codes sont valides pendant six minutes. Si vous prenez plus de six minutes pour entrer le code, vous obtiendrez un message d’erreur code non valide.

Combiner les comptes de connexion sociale et local

Vous pouvez combiner des comptes locaux et sociaux en cliquant sur votre lien de messagerie. Dans la séquence suivante, «RickAndMSFT@gmail.com » est d’abord créé en tant que connexion locale, mais vous pouvez d’abord créer le compte en tant que journal social, puis ajouter une connexion locale.

Image sélectionnant le lien d’e-mail

Cliquez sur le lien Gérer . Notez le 0 externe (connexions sociales) associé à ce compte.

Image affichant la page suivante et sélectionnant Gérer

Cliquez sur le lien vers un autre service de connexion et acceptez les demandes d’application. Les deux comptes ont été combinés. Vous pourrez vous connecter avec l’un ou l’autre compte. Vous voudrez peut-être que vos utilisateurs ajoutent des comptes locaux si leur service d’authentification de connexion sociale est en panne ou qu’ils ont probablement perdu l’accès à leur compte social.

Dans l’image suivante, Tom est un journal social (que vous pouvez voir dans la page Connexions externes : 1 ).

Image montrant les connexions externes et l’emplacement du choix d’un mot de passe

Cliquer sur Choisir un mot de passe vous permet d’ajouter un journal local associé au même compte.

Image de la page sélectionner un mot de passe

Verrouillage de compte en cas d’attaques par force brute

Vous pouvez protéger les comptes de votre application contre les attaques par dictionnaire en activant le verrouillage des utilisateurs. Le code suivant dans la ApplicationUserManager Create méthode configure le verrouillage :

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

Le code ci-dessus active le verrouillage pour l’authentification à deux facteurs uniquement. Bien que vous puissiez activer le verrouillage pour les connexions en remplaçant shouldLockout par true dans la Login méthode du contrôleur de compte, nous vous recommandons de ne pas activer le verrouillage pour les connexions, car cela rend le compte vulnérable aux attaques de connexion DOS . Dans l’exemple de code, le verrouillage est désactivé pour le compte d’administrateur créé dans la ApplicationDbInitializer Seed méthode :

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

Exiger qu’un utilisateur dispose d’un compte de messagerie validé

Le code suivant exige qu’un utilisateur dispose d’un compte de messagerie validé avant de pouvoir se connecter :

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

Comment SignInManager vérifie l’exigence 2FA

La connexion locale et le journal social dans case activée pour voir si la 2FA est activée. Si 2FA est activé, la SignInManager méthode d’ouverture de session retourne SignInStatus.RequiresVerificationet l’utilisateur est redirigé vers la SendCode méthode d’action, où il doit entrer le code pour terminer la séquence de connexion. Si l’utilisateur a RememberMe est défini sur le cookie local de l’utilisateur, le SignInManager retournera SignInStatus.Success et ils n’auront pas à passer par 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 });
    }
}

Le code suivant montre la SendCode méthode d’action. Un SelectListItem est créé avec toutes les méthodes 2FA activées pour l’utilisateur. L’élément SelectListItem est passé à l’élément DropDownListFor helper, ce qui permet à l’utilisateur de sélectionner l’approche 2FA (généralement par e-mail et 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 });
}

Une fois que l’utilisateur publie l’approche 2FA, la HTTP POST SendCode méthode d’action est appelée, le SignInManager envoie le code 2FA et l’utilisateur est redirigé vers la VerifyCode méthode d’action où il peut entrer le code pour terminer la connexion.

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

Verrouillage 2FA

Bien que vous puissiez définir le verrouillage de compte en cas d’échec de tentative de mot de passe de connexion, cette approche rend votre connexion vulnérable aux verrouillages DOS . Nous vous recommandons d’utiliser le verrouillage de compte uniquement avec 2FA. Lorsque le ApplicationUserManager est créé, l’exemple de code définit le verrouillage 2FA et MaxFailedAccessAttemptsBeforeLockout sur cinq. Une fois qu’un utilisateur se connecte (via un compte local ou un compte social), chaque tentative ayant échoué à 2FA est stockée et, si le nombre maximal de tentatives est atteint, l’utilisateur est verrouillé pendant cinq minutes (vous pouvez définir le temps de verrouillage avec DefaultAccountLockoutTimeSpan).

Ressources supplémentaires