Créer une application web ASP.NET MVC 5 sécurisée avec connexion, confirmation par e-mail et réinitialisation du mot de passe (C#)

par Rick Anderson

Ce tutoriel vous montre comment créer une application web ASP.NET MVC 5 avec confirmation par e-mail et réinitialisation de mot de passe à l’aide du système d’appartenance ASP.NET Identity.

Pour obtenir une version mise à jour de ce didacticiel qui utilise .NET Core, consultez Confirmation de compte et récupération de mot de passe dans ASP.NET Core.

Créer une application MVC ASP.NET

Commencez par installer et exécuter Visual Studio Express 2013 pour le web ou Visual Studio 2013. Installez Visual Studio 2013 Update 3 ou version ultérieure.

Notes

Avertissement : Vous devez installer Visual Studio 2013 Update 3 ou version ultérieure pour suivre ce didacticiel.

  1. Créez un projet web ASP.NET et sélectionnez le modèle MVC. Web Forms prend également en charge ASP.NET Identity. Vous pouvez donc suivre des étapes similaires dans une application de formulaires web.
    Capture d’écran montrant la page New A S P P dot Net Project. Le modèle M VC est sélectionné et comptes d’utilisateur individuels est mis en surbrillance.

  2. Laissez l’authentification par défaut comptes d’utilisateur individuels. Si vous souhaitez héberger l’application dans Azure, laissez la case case activée cochée. Plus loin dans le tutoriel, nous allons déployer sur Azure. Vous pouvez ouvrir un compte Azure gratuitement.

  3. Définissez le projet pour utiliser SSL.

  4. Exécutez l’application, cliquez sur le lien Inscrire et inscrivez un utilisateur. À ce stade, la seule validation sur l’e-mail est avec l’attribut [EmailAddress].

  5. Dans Server Explorer, accédez à Data Connections\DefaultConnection\Tables\AspNetUsers, cliquez avec le bouton droit et sélectionnez Ouvrir la définition de table.

    L’image suivante montre le AspNetUsers schéma :

    Capture d’écran montrant l’onglet Fichier de script A SP Net Users dans Server Explorer.

  6. Cliquez avec le bouton droit sur la table AspNetUsers et sélectionnez Afficher les données de table.
    Capture d’écran montrant le schéma A S P Net Users. La colonne Email Confirmé étiquetée false est mise en surbrillance.
    À ce stade, l’e-mail n’a pas été confirmé.

  7. Cliquez sur la ligne et sélectionnez Supprimer. Vous allez ajouter à nouveau cet e-mail à l’étape suivante, puis envoyer un e-mail de confirmation.

confirmation Email

Il est recommandé de confirmer l’e-mail d’une nouvelle inscription d’utilisateur pour vérifier qu’il n’emprunte pas l’identité de quelqu’un d’autre (autrement dit, il ne s’est pas inscrit auprès de l’e-mail d’une autre personne). Supposons que vous disposiez d’un forum de discussion, vous souhaitez empêcher "bob@example.com" l’inscription en tant que "joe@contoso.com". Sans confirmation par e-mail, "joe@contoso.com" peut recevoir des e-mails indésirables de votre application. Supposons que Bob s’est inscrit accidentellement en tant que "bib@example.com" et qu’il ne l’ait pas remarqué, il ne serait pas en mesure d’utiliser la récupération de mot de passe, car l’application n’a pas son adresse e-mail correcte. Email confirmation fournit uniquement une protection limitée contre les bots et ne fournit pas de protection contre les spammeurs déterminés, ils ont de nombreux alias de messagerie de travail qu’ils peuvent utiliser pour s’inscrire.

Vous souhaitez généralement empêcher les nouveaux utilisateurs de publier des données sur votre site web avant qu’elles n’aient été confirmées par e-mail, sms ou autre mécanisme. Dans les sections ci-dessous, nous allons activer la confirmation par e-mail et modifier le code pour empêcher les utilisateurs nouvellement inscrits de se connecter tant que leur e-mail n’a pas été confirmé.

Raccorder SendGrid

Les instructions de cette section ne sont pas à jour. Pour obtenir des instructions mises à jour, consultez Configurer le fournisseur de messagerie SendGrid .

Bien que ce tutoriel montre uniquement comment ajouter une notification par e-mail via SendGrid, vous pouvez envoyer des e-mails à l’aide de SMTP et d’autres mécanismes (voir ressources supplémentaires).

  1. Dans la Console du gestionnaire de package, entrez la commande suivante :

    Install-Package SendGrid
    
  2. Accédez à la page d’inscription à Azure SendGrid et inscrivez-vous pour obtenir un compte SendGrid gratuit. Configurez SendGrid en ajoutant du code similaire à ce qui suit dans App_Start/IdentityConfig.cs :

    public class EmailService : IIdentityMessageService
    {
       public async Task SendAsync(IdentityMessage message)
       {
          await configSendGridasync(message);
       }
    
       // Use NuGet to install SendGrid (Basic C# client lib) 
       private async Task configSendGridasync(IdentityMessage message)
       {
          var myMessage = new SendGridMessage();
          myMessage.AddTo(message.Destination);
          myMessage.From = new System.Net.Mail.MailAddress(
                              "Joe@contoso.com", "Joe S.");
          myMessage.Subject = message.Subject;
          myMessage.Text = message.Body;
          myMessage.Html = message.Body;
    
          var credentials = new NetworkCredential(
                     ConfigurationManager.AppSettings["mailAccount"],
                     ConfigurationManager.AppSettings["mailPassword"]
                     );
    
          // Create a Web transport for sending email.
          var transportWeb = new Web(credentials);
    
          // Send the email.
          if (transportWeb != null)
          {
             await transportWeb.DeliverAsync(myMessage);
          }
          else
          {
             Trace.TraceError("Failed to create Web transport.");
             await Task.FromResult(0);
          }
       }
    }
    

Vous devez ajouter les éléments suivants :

using SendGrid;
using System.Net;
using System.Configuration;
using System.Diagnostics;

Pour simplifier cet exemple, nous allons stocker les paramètres de l’application dans le fichier web.config :

</connectionStrings>
   <appSettings>
      <add key="webpages:Version" value="3.0.0.0" />
      <!-- Markup removed for clarity. -->
      
      <add key="mailAccount" value="xyz" />
      <add key="mailPassword" value="password" />
   </appSettings>
  <system.web>

Avertissement

Sécurité : ne stockez jamais de données sensibles dans votre code source. Le compte et les informations d’identification sont stockés dans appSetting. Sur Azure, vous pouvez stocker ces valeurs en toute sécurité sous l’onglet Configurer dans le Portail Azure. Consultez Meilleures pratiques pour le déploiement de mots de passe et d’autres données sensibles sur ASP.NET et Azure.

Activer la confirmation par e-mail dans le contrôleur de compte

//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
        var result = await UserManager.CreateAsync(user, model.Password);
        if (result.Succeeded)
        {
            await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);

            string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
            var callbackUrl = Url.Action("ConfirmEmail", "Account", 
               new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
            await UserManager.SendEmailAsync(user.Id, 
               "Confirm your account", "Please confirm your account by clicking <a href=\"" 
               + callbackUrl + "\">here</a>");

            return RedirectToAction("Index", "Home");
        }
        AddErrors(result);
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

Vérifiez que le fichier Views\Account\ConfirmEmail.cshtml a la syntaxe razor correcte. ( Le caractère @ de la première ligne est peut-être manquant. )

@{
    ViewBag.Title = "Confirm Email";
}

<h2>@ViewBag.Title.</h2>
<div>
    <p>
        Thank you for confirming your email. Please @Html.ActionLink("Click here to Log in", "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" })
    </p>
</div>

Exécutez l’application et cliquez sur le lien Inscrire. Une fois que vous avez envoyé le formulaire d’inscription, vous êtes connecté.

Capture d’écran montrant la page My A S P P dot NET Log In Home.

Vérifiez votre compte de messagerie et cliquez sur le lien pour confirmer votre e-mail.

Exiger une confirmation par e-mail avant de vous connecter

Actuellement, une fois qu’un utilisateur a complété le formulaire d’inscription, il est connecté. Vous souhaitez généralement confirmer leur e-mail avant de les connecter. Dans la section ci-dessous, nous allons modifier le code pour exiger que les nouveaux utilisateurs aient un e-mail confirmé avant qu’ils soient connectés (authentifiés). Mettez à jour la HttpPost Register méthode avec les modifications en surbrillance suivantes :

//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
   if (ModelState.IsValid)
   {
      var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
      var result = await UserManager.CreateAsync(user, model.Password);
      if (result.Succeeded)
      {
         //  Comment the following line to prevent log in until the user is confirmed.
         //  await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);

         string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
         var callbackUrl = Url.Action("ConfirmEmail", "Account",
            new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
         await UserManager.SendEmailAsync(user.Id, "Confirm your account",
            "Please confirm your account by clicking <a href=\"" + callbackUrl + "\">here</a>");

         // Uncomment to debug locally 
         // TempData["ViewBagLink"] = callbackUrl;

         ViewBag.Message = "Check your email and confirm your account, you must be confirmed "
                         + "before you can log in.";

         return View("Info");
         //return RedirectToAction("Index", "Home");
      }
      AddErrors(result);
   }

   // If we got this far, something failed, redisplay form
   return View(model);
}

En commentant la SignInAsync méthode, l’utilisateur n’est pas connecté par l’inscription. La TempData["ViewBagLink"] = callbackUrl; ligne peut être utilisée pour déboguer l’application et tester l’inscription sans envoyer d’e-mail. ViewBag.Message est utilisé pour afficher les instructions de confirmation. L’exemple de téléchargement contient du code pour tester la confirmation par e-mail sans configurer la messagerie, et peut également être utilisé pour déboguer l’application.

Créez un Views\Shared\Info.cshtml fichier et ajoutez le balisage razor suivant :

@{
   ViewBag.Title = "Info";
}
<h2>@ViewBag.Title.</h2>
<h3>@ViewBag.Message</h3>

Ajoutez l’attribut Authorize à la Contact méthode d’action du contrôleur Home. Vous pouvez cliquer sur le lien Contact pour vérifier que les utilisateurs anonymes n’ont pas accès et que les utilisateurs authentifiés y ont accès.

[Authorize]
public ActionResult Contact()
{
   ViewBag.Message = "Your contact page.";

   return View();
}

Vous devez également mettre à jour la méthode d’action HttpPost Login :

//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
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 doesn't count login failures towards account lockout
    // To enable password failures to trigger account 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, RememberMe = model.RememberMe });
        case SignInStatus.Failure:
        default:
            ModelState.AddModelError("", "Invalid login attempt.");
            return View(model);
    }
}

Mettez à jour la vue Views\Shared\Error.cshtml pour afficher le message d’erreur :

@model System.Web.Mvc.HandleErrorInfo

@{
    ViewBag.Title = "Error";
}

<h1 class="text-danger">Error.</h1>
@{
   if (String.IsNullOrEmpty(ViewBag.errorMessage))
   {
      <h2 class="text-danger">An error occurred while processing your request.</h2>
   }
   else
   {
      <h2 class="text-danger">@ViewBag.errorMessage</h2>
   }
}

Supprimez tous les comptes de la table AspNetUsers qui contiennent l’alias de messagerie que vous souhaitez tester. Exécutez l’application et vérifiez que vous ne pouvez pas vous connecter tant que vous n’avez pas confirmé votre adresse e-mail. Une fois que vous avez confirmé votre adresse e-mail, cliquez sur le lien Contact .

Récupération/réinitialisation du mot de passe

Supprimez les caractères de commentaire de la HttpPost ForgotPassword méthode d’action dans le contrôleur de compte :

//
// POST: /Account/ForgotPassword
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ForgotPassword(ForgotPasswordViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = await UserManager.FindByNameAsync(model.Email);
        if (user == null || !(await UserManager.IsEmailConfirmedAsync(user.Id)))
        {
            // Don't reveal that the user does not exist or is not confirmed
            return View("ForgotPasswordConfirmation");
        }

        string code = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
        var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
        await UserManager.SendEmailAsync(user.Id, "Reset Password", "Please reset your password by clicking <a href=\"" + callbackUrl + "\">here</a>");
        return RedirectToAction("ForgotPasswordConfirmation", "Account");
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

Supprimez les caractères de commentaire de l’ActionLinkForgotPassword dans le fichier de vue Razor Views\Account\Login.cshtml :

@using MvcPWy.Models
@model LoginViewModel
@{
   ViewBag.Title = "Log in";
}

<h2>@ViewBag.Title.</h2>
<div class="row">
   <div class="col-md-8">
      <section id="loginForm">
         @using (Html.BeginForm("Login", "Account", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
         {
            @Html.AntiForgeryToken()
            <h4>Use a local account to log in.</h4>
            <hr />
            @Html.ValidationSummary(true, "", new { @class = "text-danger" })
            <div class="form-group">
               @Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" })
               <div class="col-md-10">
                  @Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
                  @Html.ValidationMessageFor(m => m.Email, "", new { @class = "text-danger" })
               </div>
            </div>
            <div class="form-group">
               @Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" })
               <div class="col-md-10">
                  @Html.PasswordFor(m => m.Password, new { @class = "form-control" })
                  @Html.ValidationMessageFor(m => m.Password, "", new { @class = "text-danger" })
               </div>
            </div>
            <div class="form-group">
               <div class="col-md-offset-2 col-md-10">
                  <div class="checkbox">
                     @Html.CheckBoxFor(m => m.RememberMe)
                     @Html.LabelFor(m => m.RememberMe)
                  </div>
               </div>
            </div>
            <div class="form-group">
               <div class="col-md-offset-2 col-md-10">
                  <input type="submit" value="Log in" class="btn btn-default" />
               </div>
            </div>
            <p>
               @Html.ActionLink("Register as a new user", "Register")
            </p>
            @* Enable this once you have account confirmation enabled for password reset functionality *@
            <p>
               @Html.ActionLink("Forgot your password?", "ForgotPassword")
            </p>
         }
      </section>
   </div>
   <div class="col-md-4">
      <section id="socialLoginForm">
         @Html.Partial("_ExternalLoginsListPartial", new ExternalLoginListViewModel { ReturnUrl = ViewBag.ReturnUrl })
      </section>
   </div>
</div>

@section Scripts {
   @Scripts.Render("~/bundles/jqueryval")
}

La page Se connecter affiche maintenant un lien permettant de réinitialiser le mot de passe.

Une fois qu’un utilisateur a créé un compte local, un lien de confirmation lui est envoyé par e-mail avant de pouvoir se connecter. Si l’utilisateur supprime accidentellement l’e-mail de confirmation, ou si l’e-mail n’arrive jamais, il aura besoin du lien de confirmation envoyé à nouveau. Les modifications de code suivantes montrent comment l’activer.

Ajoutez la méthode d’assistance suivante au bas du fichier Controllers\AccountController.cs :

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

Mettez à jour la méthode Register pour utiliser la nouvelle assistance :

//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
   if (ModelState.IsValid)
   {
      var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
      var result = await UserManager.CreateAsync(user, model.Password);
      if (result.Succeeded)
      {
         //  Comment the following line to prevent log in until the user is confirmed.
         //  await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);

         string callbackUrl = await SendEmailConfirmationTokenAsync(user.Id, "Confirm your account");

         ViewBag.Message = "Check your email and confirm your account, you must be confirmed "
                         + "before you can log in.";

         return View("Info");
         //return RedirectToAction("Index", "Home");
      }
      AddErrors(result);
   }

   // If we got this far, something failed, redisplay form
   return View(model);
}

Mettez à jour la méthode Login pour renvoyer le mot de passe si le compte d’utilisateur n’a pas été confirmé :

//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
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);
   var user =  UserManager.Find(model.Email, model.Password);
   if (user != null)
   {
      if (!await UserManager.IsEmailConfirmedAsync(user.Id))
      {
         string callbackUrl = await SendEmailConfirmationTokenAsync(user.Id, "Confirm your account-Resend");

          // Uncomment to debug locally  
          // ViewBag.Link = callbackUrl;
         ViewBag.errorMessage = "You must have a confirmed email to log on. "
                              + "The confirmation token has been resent to your email account.";
         return View("Error");
      }
   }

   // This doesn't count login failures towards account lockout
   // To enable password failures to trigger account 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, RememberMe = model.RememberMe });
      case SignInStatus.Failure:
      default:
         ModelState.AddModelError("", "Invalid login attempt.");
         return View(model);
   }
}

Combiner des comptes de connexion sociaux et locaux

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

Capture d’écran montrant la page My A SP dot Net Log In Home. L’exemple d’ID utilisateur est mis en surbrillance.

Cliquez sur le lien Gérer . Notez les connexions externes : 0 associées à ce compte.

Capture d’écran montrant la page My A S P P dot Net Gérer votre compte. En regard de la ligne Connexions externes, 0 et un lien Gérer est mis en surbrillance.

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 à l’un ou l’autre des comptes. Vous pouvez souhaiter que vos utilisateurs ajoutent des comptes locaux au cas où leur connexion sociale dans le service d’authentification est en panne, ou plus probablement qu’ils ont 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 ).

Capture d’écran montrant la page My A S P P dot Net Gérer votre compte. Les lignes Choisir un mot de passe et Connexions externes sont mises en surbrillance.

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

Capture d’écran montrant la page My A S P dot Net Create Local Login. Un exemple de mot de passe est entré dans les champs de texte Nouveau mot de passe et Confirmer le nouveau mot de passe.

Email confirmation plus approfondie

Mon tutoriel Confirmation de compte et récupération de mot de passe avec ASP.NET Identity aborde cette rubrique avec plus de détails.

Débogage de l’application

Si vous ne recevez pas d’e-mail contenant le lien :

  • Vérifiez votre dossier de courrier indésirable ou de courrier indésirable.
  • Connectez-vous à votre compte SendGrid et cliquez sur le lien activité Email.

Pour tester le lien de vérification sans e-mail, téléchargez l’exemple terminé. Le lien de confirmation et les codes de confirmation s’affichent sur la page.

Ressources supplémentaires