https://d226lax1qjow5r.cloudfront.net/blog/blogposts/securing-asp-net-app-2fa-using-nexmo-sms-sendgrid-email-dr/2fa-with-sms-or-email.png

Sécuriser votre application ASP.NET avec 2FA en utilisant Nexmo SMS et SendGrid Email

Publié le May 17, 2021

Temps de lecture : 6 minutes

Le 2FA (2 Factor Authentication) est aujourd'hui incontournable pour renforcer la sécurité au sein de votre application. On la retrouve dans toutes sortes d'applications : du processus d'inscription à la vérification des actions de l'utilisateur. Les types de 2FA les plus courants sont la vérification par téléphone et la vérification par email.

Dans ce tutoriel, nous verrons comment mettre en place 2FA dans votre application .NET en utilisant ASP .NET Identity, la Nexmo C# Client Library pour l'authentification par SMS et la bibliothèque C# de SendGrid C# Client pour l'authentification par email.

Si vous voulez simplement voir le résultat, vous pouvez jeter un coup d'œil à la vidéo suivante la Video ou télécharger le code.

Mise en place d'une application ASP.NET MVC

Ouvrez Visual Studio et créez une nouvelle application ASP .NET MVC. Pour cette démonstration, nous allons supprimer les sections Contact et À propos du site Web généré par défaut.

Installer le client Nexmo dans votre application via le gestionnaire de paquets NuGet

Ajoutez le client Nexmo à votre application via la console de paquets NuGet.

PM> Install-Package Nexmo.Csharp.Client

Installer le client SendGrid via le gestionnaire de paquets NuGet

PM> Install-Package SendGrid -Version 8.0.3

Ajouter les identifiants Nexmo et SendGrid

Pour les besoins de la démonstration, nous placerons les identifiants Nexmo et SendGrid dans la section <appSettings> du fichier Web.config du fichier. Si nous développions cette application pour la distribuer, nous pourrions choisir d'entrer ces identifiants dans notre portail Azure.

<add key="Nexmo.Url.Rest" value="https://rest.nexmo.com" />
<add key="Nexmo.Url.Api" value="https://api.nexmo.com" />
<add key="Nexmo.api_key" value="NEXMO_API_KEY" />
<add key="Nexmo.api_secret" value="NEXMO_API_SECRET" />
<add key="SMSAccountFrom" value="SMS_FROM_NUMBER" />
<add key="mailAccount" value="SENDGRID_API_KEY" />

Branchez Nexmo dans le service SMS, SendGrid dans le service Email

Dans le fichier IdentityConfig.cs ajoutez la configuration SendGrid dans la méthode SMSService dans la méthode Ensuite, ajoutez le client Nexmo dans la méthode SMSService du fichier IdentityConfig.cs du fichier.

N'oubliez pas d'ajouter les directives using pour les fichiers Nexmo.Api et SendGrid et tous les autres espaces de noms signalés comme manquants.

public class EmailService : IIdentityMessageService
{
    public async Task SendAsync(IdentityMessage message)
    {
        // Plug in your email service here to send an email.
        await configSendGridasync(message);
    }
    private async Task configSendGridasync(IdentityMessage message)
    {
        string apiKey = ConfigurationManager.AppSettings["mailAPIKey"];       
        dynamic sg = new SendGridAPIClient(apiKey, "https://api.sendgrid.com");
        
        Email from = new Email("demo@nexmo.com");
        string subject = message.Subject;
        Email to = new Email(message.Destination);
        Content content = new Content("text/plain", message.Body);
        Mail mail = new Mail(from, subject, to, content); 

        dynamic response = await sg.client.mail.send.post(requestBody: mail.Get()); 
    }
}

public class SmsService : IIdentityMessageService
{
    public Task SendAsync(IdentityMessage message)
    {
        var sms = SMS.Send(new SMS.SMSRequest
        {
            from = ConfigurationManager.AppSettings["SMSAccountFrom"],
            to = message.Destination,
            text = message.Body
        });
        return Task.FromResult(0);
    }
}

Ajouter la méthode 'SendEmailConfirmationTokenAsync()' à 'AccountController'.

Ajoutez la méthode suivante à votre AccountController qui sera appelée lors de l'enregistrement de l'utilisateur pour envoyer un courriel de confirmation à l'adresse électronique fournie.

private async Task<string> SendEmailConfirmationTokenAsync(string userID, string subject)
{
    string code = await UserManager.GenerateEmailConfirmationTokenAsync(userID);
    var callbackUrl = Url.Action("ConfirmEmail", "Account",  new { userId = userID, code = code }, protocol: Request.Url.Scheme);
    await UserManager.SendEmailAsync(userID, subject, "Please confirm your account by clicking <a href="" + callbackUrl + "">here</a>");
    return callbackUrl;
}

Mise à jour de la méthode d'action "Enregistrer

A l'intérieur de la méthode Register de la méthode AccountControllerajoute quelques propriétés à la variable nouvellement créée du type ApplicationUser : TwoFactorEnabled (true), PhoneNumberConfirmed (false). Une fois l'utilisateur créé avec succès, stockez l'identifiant de l'utilisateur dans un état de session et redirigez l'utilisateur vers la méthode d'action AddPhoneNumber dans la méthode d'action ManageController.

[AllowAnonymous]
public ActionResult AddPhoneNumber()
{
    return View();
}

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = new ApplicationUser { UserName = model.Email, Email = model.Email, TwoFactorEnabled = true, PhoneNumberConfirmed = false};
        var result = await UserManager.CreateAsync(user, model.Password);
        if (result.Succeeded)
        {
            Session["UserID"] = user.Id;
            return RedirectToAction("AddPhoneNumber", "Manage");
        }
        AddErrors(result);
    }
    // If we got this far, something failed, redisplay form
    return View(model);
}

Vérifier l'existence d'un numéro de téléphone dans la base de données et ajouter la logique SMS à la méthode d'action AddPhoneNumber.

Dans le ManageController ajoutez l'attribut [AllowAnonymous] aux méthodes d'action GET et POST AddPhoneNumber . Cela permet à l'utilisateur non enregistré d'accéder au processus de confirmation du numéro de téléphone. Effectuez une requête dans la base de données pour vérifier si le numéro de téléphone saisi par l'utilisateur est déjà associé à un Account. Si ce n'est pas le cas, redirigez l'utilisateur vers la méthode d'action VerifyPhoneNumber méthode d'action.

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> AddPhoneNumber(AddPhoneNumberViewModel model)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }
    var db = new ApplicationDbContext();
    if (db.Users.FirstOrDefault(u => u.PhoneNumber == model.Number) == null)
    {
        // Generate the token and send it
        var code = await UserManager.GenerateChangePhoneNumberTokenAsync((string)Session["UserID"], 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 });
    }
    else
    {
        ModelState.AddModelError("", "The provided phone number is associated with another account.");
        return View();
    }
}

Mise à jour de la méthode d'action VerifyPhoneNumber

Ajoutez l'attribut [AllowAnonymous] à la méthode d'action GET et supprimez tout ce qui se trouve dans la méthode, à l'exception de l'instruction de retour qui dirige le flux de vérification,

[AllowAnonymous]
public async Task<ActionResult> VerifyPhoneNumber(string phoneNumber)
{
    return phoneNumber == null ? View("Error") : View(new VerifyPhoneNumberViewModel { PhoneNumber = phoneNumber });
}

Remplacer User.Identity.GetUserId() par Session["UserID"] dans la méthode, comme indiqué ci-dessous. Si l'utilisateur saisit avec succès le code pin, il est dirigé vers la vue Index de l'application ManageController. La propriété booléenne de l'utilisateur PhoneNumberConfirmed de l'utilisateur est alors définie à true.

[AllowAnonymous]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> VerifyPhoneNumber(VerifyPhoneNumberViewModel model)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }
    var result = await UserManager.ChangePhoneNumberAsync((string)Session["UserID"], model.PhoneNumber, model.Code);
    if (result.Succeeded)
    {
        var user = await UserManager.FindByIdAsync((string)Session["UserID"]);
        if (user != null)
        {
            await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: 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);
}

Vérifier si l'utilisateur a un courriel confirmé lors de la connexion

Retour dans le AccountControllermettre à jour la méthode d'action Login() pour vérifier si l'utilisateur a confirmé son adresse électronique ou non. Si ce n'est pas le cas, renvoyer un message d'erreur et rediriger l'utilisateur vers la vue "Info". Appelez également la méthode SendEmailConfirmationTokenAsync() en lui transmettant la méthode user.Id et l'objet de l'e-mail.

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }

    var user = await UserManager.FindByNameAsync(model.Email);
    if (user != null)
    {
        if (!await UserManager.IsEmailConfirmedAsync(user.Id))
        {
            string callbackUrl = await SendEmailConfirmationTokenAsync(user.Id, "Confirm your account");
            ViewBag.title = "Check Email";
            ViewBag.message = "You must have a confirmed email to login.";
            return View("Info");
        }
    }

    ...

Ajouter une vue d'information

A l'intérieur du Views/Accountcréez une nouvelle vue nommée Info vers laquelle l'utilisateur sera redirigé si son courriel n'a pas été confirmé. La vue doit contenir le code suivant :

<h2>@ViewBag.Title.</h2>
<h3>@ViewBag.Message</h3>

S'assurer que le système 2FA ne peut pas être contourné

Dans le Views/Account/Login.cshtml supprimez l'élément <div class="form-group"> contenant la case à cocher "Se souvenir de moi". En Views/Account/VerifyCode.cshtml supprimez l'élément <div class="form-group"> pour la case à cocher "RememberBrowser" et l'entrée RememberMe cachée. Supprimez la variable correspondante dans chacun des modèles de vue en AccountViewModels.cs: SendCodeViewModel et VerifyCodeViewModel. Enfin, supprimez toute utilisation de ces variables (y compris les signatures de méthodes) ou, le cas échéant, remplacez l'utilisation de ces variables dans les deux modèles par des variables false. Cela empêchera l'utilisateur de contourner la vérification 2FA.

Conclusion

Vous disposez ainsi d'une application web utilisant ASP .NET Identity qui est dotée d'une authentification à deux facteurs (2FA) utilisant Nexmo SMS et SendGrid Email comme différentes méthodes de vérification.

Le SMS et l'email fournissent des couches de sécurité supplémentaires pour identifier correctement les utilisateurs et protéger davantage les informations sensibles des utilisateurs. En utilisant la bibliothèque Nexmo C# Client et le client C# de SendGrid, vous pouvez ajouter des vérifications par SMS et par e-mail en toute simplicité.

S'il vous plaît saisir le code et essayez-le par vous-même.

N'hésitez pas à me faire part de vos réflexions/questions sur Twitter @sidsharma_27 ou envoyez-moi un courriel à l'adresse sidharth.sharma@nexmo.com!

Partager:

https://a.storyblok.com/f/270183/150x150/a3d03a85fd/placeholder.svg
Sidharth Sharma