glasses-logo

<dominikgorecki> Web development on the Microsoft Stack <dominikgorecki>

Implementing Password Reset in MVC 4 EF Code First using Simple Membership – Part 2

I apologize for the long delay between part 1 and part 2, but I’ve been really busy. I’ve created 2 new jQuery plugins (scrolling related) and I’ve put out my own RWD Grid Framework called Bare Bones. Check them out!

So it took me a while to figure out where we left off and what the next step is. Before you continue with this tutorial, you should already have a working copy of where we ended up with Part 1: an extended account model that allows for email entry. That was the hard part.  In Part 2, I’m going to explain how to use WebSecurity.GeneratePasswordResetToken that will create a token used to reset a user’s password. This is currently the most common way to recover a password: an email is sent to the user’s verified email address with a link the user can use to reset their password.

Part II – Create the Reset Functionality

The great part is that Simple Membership already comes with the functionality built in so all we need to do is implement it. Now that we have an email in our membership from Part 1, we’ll first need to create the request for reset page, then send the email, and finally a page to actually reset the password.  As suggested by Alec on my Code First tutorial, I will wrap up all the DB contexts in “using” statements to ensure it is properly disposed of as soon as it’s not needed.

Step 1 : Setup the LostPassword Page

Create the GET action in AccountController for LostPassword that simply returns View():

// GET: Account/LostPassword
[AllowAnonymous]
public ActionResult LostPassword()
{
    return View();
}

Then create the view that allows for the user to input their email address to receive their reset link. Note: Some websites like you to type in your username instead of your email address; however, I like the ones that allow you to enter your email address because–as sometimes happens to me–the user may have forgotten their username as well! Also if you want to do things right, I would suggest creating a LostPasswordModel that can be used on the view page so you can create validate it against the Email DataType and create custom error messages:

LostPasswordModel in AccountModels.cs:

public class LostPasswordModel
 {
    [Required(ErrorMessage = "We need your email to send you a reset link!")]
    [Display(Name = "Your account email")]
    [EmailAddress(ErrorMessage= "Not a valid email--what are you trying to do here?")]
    public string Email { get; set; }
 }

Create a new view based on the GET action we created (LostPassword.cshtml):

@model PasswordResetApp.Models.LostPasswordModel
@{
    ViewBag.Title = "Lost Password";
}

<h2>Lost Password</h2>

@using (Html.BeginForm())
{
   @Html.AntiForgeryToken()
   @Html.ValidationSummary()
   <fieldset>
      <legend>Lost Password Form</legend>
      <ol>
         <li>
             @Html.LabelFor(m => m.Email)
             @Html.TextBoxFor(m => m.Email)
         </li>
      </ol>
      <input type="submit" value="Recover Account" />
   </fieldset>
}

Step 2: Process the LostPassword POST

Create the POST part of the LostPassword action. This is a little tricky because you would think that you could use the Membership.GetUserNameByEmail method, but you can’t since that in Simple Membership that is not implemented. We will need to first look-up the username with a Linq query to load the MembershipUser object with (line 11 in our example). After that you generate the token (line 26) and send the email (line 49). Read the comments in this example carefully. The first comment on line 58 deals with privacy: you may not want to let the end user know that the email has not been found since that gives a third party possibly valuable information. The next comment on line 69 talks about how you may want to redirect to a “success” page since, right now, this example does not do anything if the email is successful sent. I didn’t want to add too much superfluous crap to this example; I leave the UX\flow up to you.

Here is the POST action for LostPassword in AccountController.cs:

// POST: Account/LostPassword
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult LostPassword(LostPasswordModel model)
{
   if (ModelState.IsValid)
   {
      MembershipUser user;
      using (var context = new UsersContext())
      {
         var foundUserName = (from u in context.UserProfiles
                              where u.Email == model.Email
                              select u.UserName).FirstOrDefault();
         if (foundUserName != null)
         {
            user = Membership.GetUser(foundUserName.ToString());
         }
         else
         {
            user = null;
         }
      }
      if (user != null)
      {
         // Generae password token that will be used in the email link to authenticate user
         var token = WebSecurity.GeneratePasswordResetToken(user.UserName);
         // Generate the html link sent via email
         string resetLink = "<a href='"
            + Url.Action("ResetPassword", "Account", new { rt = token }, "http") 
            + "'>Reset Password Link</a>";

         // Email stuff
         string subject = "Reset your password for asdf.com";
         string body = "You link: " + resetLink;
         string from = "[email protected]";

         MailMessage message = new MailMessage(from, model.Email);
         message.Subject = subject;
         message.Body = body;
         SmtpClient client = new SmtpClient();

         // Attempt to send the email
         try
         {
            client.Send(message);
         }
         catch (Exception e)
         {
            ModelState.AddModelError("", "Issue sending email: " + e.Message);
         }
      }         
      else // Email not found
      {
         /* Note: You may not want to provide the following information
         * since it gives an intruder information as to whether a
         * certain email address is registered with this website or not.
         * If you're really concerned about privacy, you may want to
         * forward to the same "Success" page regardless whether an
         * user was found or not. This is only for illustration purposes.
         */
         ModelState.AddModelError("", "No user found by that email.");
      }
   }

   /* You may want to send the user to a "Success" page upon the successful
   * sending of the reset email link. Right now, if we are 100% successful
   * nothing happens on the page. :P
   */
   return View(model);
}

Step 3: Set-up SMTP

Temporarily set the SMTP settings to create a file in a directory so you can test out your solution (later you’ll want to set-up a valid SMTP server here so your application can actually send the email). Go into you main Web.Config and add this at the bottom (I show the </configuration> tag to illustrate the placement):

   <system.net>
      <mailSettings>
         <smtp deliveryMethod="SpecifiedPickupDirectory">
            <specifiedPickupDirectory pickupDirectoryLocation="C:\email"/>
         </smtp>
      </mailSettings>
   </system.net>
</configuration>

You’ll also need to create a new directory, “C:\email”. Once you’re done with this step, you’re application should successfully create an email file in C:\email that should contain the reset URL. The reset URL will still not work as we’ll need to

Step 4: Reset Password Page View Model

The final part is to create a page that processes the username (un) and token (rt) to allow the user to enter a new password.  This isn’t too difficult. First I create a view model for the reset page that will ensure that a return token is entered and that a new password is entered and confirmed (AccoundModels.cs):

public class ResetPasswordModel
{
   [Required]
   [Display(Name = "New Password")]
   [DataType(DataType.Password)]
   public string Password { get; set; }

   [Required]
   [Display(Name = "Confirm Password")]
   [DataType(DataType.Password)]
   [Compare("Password", ErrorMessage = "New password and confirmation does not match.")]
   public string ConfirmPassword { get; set; }

   [Required]
   public string ReturnToken { get; set; }
}

Step 5: Create the GET Action and Page

The actions are quite simple because Simple Membership exposes a handy method for reseting the password (WebSecurity.ResetPassword) that takes two parameters: 1. the generated token and 2. The new password. First, however, we’ll need to create the GET action that will taken in the necessary query parameter and assigns it to the ReturnToken of the ResetPasswordModel model (AccountController.cs):

// GET: /Account/ResetPassword
[AllowAnonymous]
public ActionResult ResetPassword(string rt)
{
   ResetPasswordModel model = new ResetPasswordModel();
   model.ReturnToken = rt;
   return View(model);
}

From this create the view based on the ResetPasswordModel model view. Everything here is pretty standard except for the hidden field on line 25. This hidden field is passed in through the GET action that assigns it from the query parameter and allows the field to be entered in as a regular form to the POST method (ResetPassword.cshtml):

@model PasswordResetApp.Models.ResetPasswordModel

@{
   ViewBag.Title = "ResetPassword";
}

<h2>Reset Password</h2>

@using (Html.BeginForm())
{
   @Html.AntiForgeryToken()
   @Html.ValidationSummary()

   <fieldset>
      <legend>Resetting password form</legend>
      <ol>
         <li>
            @Html.LabelFor(m => m.Password)
            @Html.PasswordFor(m => m.Password)
         </li>
         <li>
            @Html.LabelFor(m => m.ConfirmPassword)
            @Html.PasswordFor(m => m.ConfirmPassword)
         </li>
         @Html.HiddenFor(m => m.ReturnToken)
      </ol>
      <input type="submit" value="Reset" />
   </fieldset>
<h2>@ViewBag.Message</h2>
}

Step 6: Create the POST action for ResetPassword

For the final step we’ll use the WebSecurity.ResetPassword method to change the users password. One line. Simple as that (AccountController.cs):

// POST: /Account/ResetPassword
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ResetPassword(ResetPasswordModel model)
   {
   if (ModelState.IsValid)
   {
      bool resetResponse = WebSecurity.ResetPassword(model.ReturnToken, model.Password);
      if (resetResponse)
      {
         ViewBag.Message = "Successfully Changed";
      }
      else
      {
         ViewBag.Message = "Something went horribly wrong!";
      }
   }
return View(model);
}

Notice a few things here. The expected parameter is the model being returned, which is the view model we created for the ResetPage. Next, notice the one line that does all the work here–line 9.

Step 7: Test

So now that you have a working password reset, it’s time to test it. If you don’t have a user created yet, then create one. Test it out with one password and log-out. The go to /Account/LostPassword and type in the email that you registered the account with. Check “C:\email” for the email (go by last modified) and copy the URL into your client. … well you know the rest.

14 Responses to “Implementing Password Reset in MVC 4 EF Code First using Simple Membership – Part 2”

  1. Nancy says:

    Great article. You have done good job. I really enjoy.
    Pc Help

  2. Gopal Gajjar says:

    Great step by step info.
    great formatting of the website.
    keep it up..

  3. Birgit says:

    Hello, I wish for to subscribe for this weblog to take hottest updates, therefore where can i do it please assist.

  4. leyla says:

    Hi, i want know where is WebSecurity.GeneratePasswordResetToken(user.UserName);

    using doesnot exist GeneratePasswordResetToken method

  5. Randy says:

    This is exactly what I needed. Thanks for an excellent post. I have been looking for a similar example of how to implement user/role management in MVC 4. Couldn’t find anything so I ended up doing my own.

    Thanks for all the time savings.

    The only thing I really needed to change was to add
    message.IsBodyHtml = true; so that the link formats correctly in the email message in outlook.

  6. Dominik,

    I just wanted to tell you what an excellent series this has been. I saved many hours (and strands of hair) by following your examples.

    A couple of notes…

    When composing the email link message, I had to specify the following in order to work with Gmail:

    MailMessage message = new MailMessage(from, to);
    message.Subject = subject;
    message.Body = body;
    message.BodyEncoding = Encoding.UTF8;
    message.IsBodyHtml = true;

    I found it useful to be specific about the delivery method if you have to use SSL:

    SmtpClient client = new SmtpClient
    {
    Host = “smtp.gmail.com”,
    Port = 587,
    EnableSsl = true,
    UseDefaultCredentials = false,
    Credentials = new System.Net.NetworkCredential(“[email protected]”, “pwdgoeshere”),
    DeliveryMethod = SmtpDeliveryMethod.Network
    };

    Again, thanks for an excellent article and your great writing.

    • Dominik says:

      Thank you for the compliment! I have been really busy with work and I haven’t been able to contribute as much as I would like lately, but I hope to be more active with the blog.

      Thanks again!

  7. sudeep says:

    Hi,

    Thank you so much for such a great article.

    I just implemented this in my application,and i am stuck with a problem now.
    The line
    Membership.GetUser(foundUserName.ToString())
    always returns null.
    I have found in some forums that Membership.GetUser() will only work for an authenticated user. Otherwise, it’s going to return null.

    So please provide me any suggestions to overcome this issue.

    Thanks again…

    • Dominik says:

      That’s weird. The solution worked perfect for me (I wrote it and tested it before writing the article). Perhaps this is something new in the current MVC release. Try ensuring that the controller that looks up the user has authentication turned off. If I have time, I’ll look into it further.

      If you figure it out, let me know what worked for you.

      Thanks!
      Dominik

  8. ganesh says:

    Dominik,
    Very nice article. You have done good job.

  9. MJamal says:

    Thank you so much for such a beautiful article, easy to implement and easy to understand. Helped a lot.

    Are there any other websites that you post your articles on. If so please let me know.

    Thanks a ton.

  10. Amir says:

    i am so grateful for this tutorial it save many hour of search an read really thanks i just want to know what
    WebSecurity.ResetPassword

    do because i am using my own authorization and authentication system i can apply all of this but can’t apply
    WebSecurity.ResetPassword
    second how can i make the token link expire ?

Leave a Reply

Open Menu Button