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 web ou Visual Studio 2013. Installez Visual Studio 2013 Update 3 ou une version ultérieure.

Notes

Avertissement : Vous devez installer Visual Studio 2013 Update 3 ou une 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 web forms.
    Capture d’écran montrant la page New A S P dot Net Project. Le modèle M V C est sélectionné et les comptes d’utilisateur individuels sont mis en surbrillance.

  2. Conservez l’authentification par défaut en tant que comptes d’utilisateur individuels. Si vous souhaitez héberger l’application dans Azure, cochez la case. Plus loin dans le tutoriel, nous allons déployer sur Azure. Vous pouvez ouvrir gratuitement un compte Azure.

  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 associée à l’attribut [EmailAddress].

  5. Dans l’Explorateur de serveurs, accédez à Connexions de données\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 des utilisateurs A S P Net dans l’Explorateur de serveurs.

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

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

Confirmation par e-mail

Il est recommandé de confirmer l’e-mail d’une nouvelle inscription d’utilisateur pour vérifier qu’il n’emprunte pas l’identité d’une autre personne (autrement dit, il n’a pas été inscrit auprès de l’e-mail d’une autre personne). Supposons que vous aviez un forum de discussion, vous voudriez empêcher "bob@example.com" l’inscription en tant que "joe@contoso.com". Sans confirmation par e-mail, "joe@contoso.com" il est possible d’obtenir un e-mail indésirable à partir de votre application. Supposons que Bob s’est inscrit accidentellement comme "bib@example.com" et qu’il ne l’avait 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 e-mail correct. La confirmation par e-mail 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’ils n’aient été confirmés par e-mail, par sms ou par un 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 jusqu’à ce que leur e-mail ait été confirmé.

Raccorder SendGrid

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

Bien que ce didacticiel montre uniquement comment ajouter une notification par e-mail via SendGrid, vous pouvez envoyer un e-mail à 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 à 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 les meilleures pratiques pour déployer des 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 une syntaxe razor correcte. ( Le caractère @ de la première ligne 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, puis cliquez sur le lien Inscrire. Une fois que vous avez envoyé le formulaire d’inscription, vous êtes connecté.

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

Vérifiez votre compte de messagerie, puis 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 terminé 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 ne soient connectés (authentifiés). Mettez à jour la HttpPost Register méthode avec les modifications mises 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 ne sera 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 de courrier électronique. ViewBag.Message est utilisé pour afficher les instructions de confirmation. L’exemple de téléchargement contient du code pour tester la confirmation de l’e-mail sans configurer l’e-mail 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 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 avec lequel vous souhaitez effectuer un test. 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 méthode d’action HttpPost ForgotPassword 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 d’affichage 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 De connexion affiche désormais un lien pour réinitialiser le mot de passe.

Une fois qu’un utilisateur crée un compte local, il est envoyé par e-mail un lien de confirmation qu’il doit utiliser 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 le nouvel 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 De connexion 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 de messagerie. Dans la séquence suivante, la séquence RickAndMSFT@gmail.com suivante est créée 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 d’accueil My A S P dot Net Log In. 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 dot Net Manage your account. 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 serez en mesure de vous connecter avec l’un ou l’autre des comptes. Vous souhaiterez peut-être 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 à partir des connexions externes : 1 affiché sur la page).

Capture d’écran montrant la page My A S P dot Net Manage your account. Les lignes Choisir un mot de passe et des connexions externes sont mises en surbrillance.

En cliquant sur Choisir un mot de passe , vous pouvez 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 le nouveau mot de passe et confirmez les nouveaux champs de texte du mot de passe.

Confirmation par e-mail plus approfondie

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

Débogage de l’application

Si vous n’obtenez 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, puis cliquez sur le lien Activité de messagerie.

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