Enable Password Resetting with Simple Membership in MVC 4

In this article you will learn how to enable password resetting (in case user forgot the password) with Simple Membership in MVC. User needs to type his username and system will check its existence, if found correct this will send an email containing dynamically generated URL with username and password reset token.

Before start with coding, let’s look at quick demo on YouTube.


This article is going to be quite long, so I will write it in two sections:

i) Sending Password Reset Information via Email
ii) Receiving Password Reset Information from URL

Both section will have step by step approach to help you understand which thing to do first.

Before following the steps given below, create a new MVC 4 Application with Internet Application template and then try running and creating a new user account, this will generate Simple Membership Database Tables.

i) Sending Password Reset Information via Email

Step 1

As you know, we don’t see an email field in a user registration form as well as in the membership database. But we can enable this using some quick changes in the application. Let’s make some changes in DB.


I added two new fields EmailId and Details.

Step 2

Now I want above newly added field’s information to be filled by user when he register on the website so, I need to update Register View Model as well as Register View. Here is the updated Register View Model.

public class RegisterModel
{
    [Required]
    [Display(Name = "User name")]
    public string UserName { get; set; }

    [Required]
    [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
    [DataType(DataType.Password)]
    [Display(Name = "Password")]
    public string Password { get; set; }

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

    //new properties
    [Required]
    [Display(Name="Email ID")]
    public string EmailId { get; set; }

    [Required]
    [Display(Name = "About Yourself")]
    public string Details { get; set; }
}

See (highlighted above), I have added two new properties above to enable strongly typed for Register View. Here it is:

@model MvcMembership.Models.RegisterModel
@{
    ViewBag.Title = "Register";
}

<hgroup class="title">
    <h1>@ViewBag.Title.</h1>
    <h2>Create a new account.</h2>
</hgroup>

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

    <fieldset>
        <legend>Registration Form</legend>
        <ol>
            <li>
                @Html.LabelFor(m => m.UserName)
                @Html.TextBoxFor(m => m.UserName)
            </li>
            <li>
                @Html.LabelFor(m => m.Password)
                @Html.PasswordFor(m => m.Password)
            </li>
            <li>
                @Html.LabelFor(m => m.ConfirmPassword)
                @Html.PasswordFor(m => m.ConfirmPassword)
            </li>
            <li>
                @Html.LabelFor(m => m.EmailId)
                @Html.TextBoxFor(m => m.EmailId)
            </li>
            <li>
                @Html.LabelFor(m => m.Details)
                @Html.TextBoxFor(m => m.Details)
            </li>
        </ol>
        <input type="submit" value="Register" />
    </fieldset>
}

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

I highlighted the changed made in above code. Now, when user will hit ‘Register’ button on Register view a POST request will happen containing UserName, Password, EmailId, Details.

Step 3

Now, I need to make some changes in POST version of Register controller.

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Register(RegisterModel model)
{
    if (ModelState.IsValid)
    {
        // Attempt to register the user
        try
        {
            WebSecurity.CreateUserAndAccount(model.UserName, model.Password, new { EmailId = model.EmailId, Details = model.Details});
            WebSecurity.Login(model.UserName, model.Password);
            return RedirectToAction("Index", "Home");
        }
        catch (MembershipCreateUserException e)
        {
            ModelState.AddModelError("", ErrorCodeToString(e.StatusCode));
        }
    }

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

I highlighted the changes made in above code. You can see this controller will accept a ‘model’ of type RegisterModel that you can see in step 2.

Please note, this controller will do three things, create a new user account, login and redirect on Index view of Home controller.


So, user is registered now and there is email and details information up in my database.

Step 4

Now, we are ready to implement the password reset functionality for above modified application.

First let’s display a link to user on login page.


When user will click on above ‘Recover’ link, he will be redirect to a new view ‘ForgotPassword’, you need to create this view and its GET and POST methods.

Step 5

Before creating ‘ForgotPassword’ view you need GET and POST version action methods in controller, I will create both in AccountController.

So, the GET version of the action method is here.

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

And it will return following view page.

@{
    ViewBag.Title = "Forgot Password";
}

<h2>Forgot Password</h2>

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
    <fieldset>
        <legend>Forgot Password Form</legend>
        <ol>
            <li>
                @Html.Label("User Name", new { @for = "UserName" })
                @Html.TextBox("UserName")
                <span style="color:red;">@TempData["Message"]</span>
            </li>
        </ol>
        <input type="submit" value="Recover" />
    </fieldset>
}

That’s it. Notice three things, a TextBox by name ‘UserName’, a <span> to display the message in red color, and a button (input) to submit the UserName with POST request.

So, the POST version of ‘ForgotPassword’ action method which accepts ‘UserName’ from POST request made by above view page is here.

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ForgotPassword(string UserName)
{
    //check user existance
    var user = Membership.GetUser(UserName);
    if (user == null)
    {
        TempData["Message"] = "User Not exist.";
    }
    else
    {
        //generate password token
        var token = WebSecurity.GeneratePasswordResetToken(UserName);
        //create url with above token
        var resetLink = "<a href='" + Url.Action("ResetPassword", "Account", new { un = UserName, rt = token }, "http") + "'>Reset Password</a>";
        //get user emailid
        UsersContext db = new UsersContext();
        var emailid = (from i in db.UserProfiles
                        where i.UserName == UserName
                        select i.EmailId).FirstOrDefault();
        //send mail
        string subject = "Password Reset Token";
        string body = "<b>Please find the Password Reset Token</b><br/>" + resetLink; //edit it
        try
        {
            SendEMail(emailid, subject, body);
            TempData["Message"] = "Mail Sent.";
        }
        catch (Exception ex)
        {
            TempData["Message"] = "Error occured while sending email." + ex.Message;
        }
        //only for testing
        TempData["Message"] = resetLink;
    }
           
    return View();
}

In above action method with the received UserName from POST call I will check for username existence in DB, if not found will display ‘User Not exist.’ with the help of TempData. In case username matches, WebSecurity.GeneratePasswordResetToken will generate a password reset token and put it in the membership database for matching username. After generating password reset token it will generate a URL containing username and password reset token that will be sent via email [Read more in step 6]. Also note, I am using ResetPassword action method of Account controller that we have not implemented so far, you will see it later in this article. At the end I called a method ‘SendMail’ by passing emailed (grabbed from db with linq query), subject and body (containing generated URL) as a parameters.

Step 6

Now, I have URL (containing username and token) to send via email but we don’t have email in scope. So, I created an instance of UserProfile DB Model (you get this automatically when simple membership database comes-up) using UsersContext db = new UsersContext(), you can see it in above POST version of ‘ForgotPassword’ controller.

Here is the DbContext that we get for membership database.

public class UsersContext : DbContext
{
    public UsersContext()
        : base("DefaultConnection")
    {
    }

    public DbSet<UserProfile> UserProfiles { get; set; }
    //newly added
    //public DbSet<webpages_Membership> webpages_Memberships { get; set; }
}

In the Linq query you can see how I’m getting intellisense support for my EmailId and Details field also.


Actually, this is not default, I made some changes in UserProfile View Model, which is here.

[Table("UserProfile")]
public class UserProfile
{
    [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int UserId { get; set; }
    public string UserName { get; set; }
    //new properties
    public string EmailId { get; set; }
    public string Details { get; set; }
}

I highlighted the changes made in above code.


So, far we sent the email containing URL to reset password. Now, user is going to click on the URL we sent via mail and we need to deal with that incoming GET call and verify them by matching username and token (by grabbed data from URL).

ii) Receiving Password Reset Information from URL

Step 1

We need a GET action method by name ‘ResetPassword’ in ‘Account’ controller because we sent this via mail (Read Setp 5 above). Let’s implement it.

[AllowAnonymous]
public ActionResult ResetPassword(string un, string rt)
{
    UsersContext db = new UsersContext();
    //TODO: Check the un and rt matching and then perform following
    //get userid of received username
    var userid = (from i in db.UserProfiles
                    where i.UserName == un
                    select i.UserId).FirstOrDefault();
    //check userid and token matches
    bool any = (from j in db.webpages_Memberships
                where (j.UserId == userid)
                && (j.PasswordVerificationToken == rt)
                //&& (j.PasswordVerificationTokenExpirationDate < DateTime.Now)
                select j).Any();

    if (any == true)
    {
        //generate random password
        string newpassword = GenerateRandomPassword(6);
        //reset password
        bool response = WebSecurity.ResetPassword(rt, newpassword);
        if (response == true)
        {
            //get user emailid to send password
            var emailid = (from i in db.UserProfiles
                            where i.UserName == un
                            select i.EmailId).FirstOrDefault();
            //send email
            string subject = "New Password";
            string body = "<b>Please find the New Password</b><br/>" + newpassword; //edit it
            try
            {
                SendEMail(emailid, subject, body);
                TempData["Message"] = "Mail Sent.";
            }
            catch (Exception ex)
            {
                TempData["Message"] = "Error occured while sending email." + ex.Message;
            }

            //display message
            TempData["Message"] = "Success! Check email we sent. Your New Password Is " + newpassword;
        }
        else
        {
            TempData["Message"] = "Hey, avoid random request on this page.";
        }
    }
    else
    {
        TempData["Message"] = "Username and token not maching.";
    }           

    return View();
}

In above code, at very first you can see this method is accepting ‘un’ (which is username) and ‘rt’ (which is password reset token) from the URL.

Then, I created an instance of UserProfile DB Model that will allow me linq query with intellisense.

In the image given below you can see we have UserName in UserProfile Table and PasswordVerificationToken in webpages_Membership Table.


As you know we have UserName and Token (grabbed from URL). So, how will you match both, the best way is by using Stored Procedure but I don’t want to make this long article a book, lol. What I’m going to do is I will get UserId from UserProfile Table (look at first Linq query) and then will match the UserId and Token (look at second Linq query).

The second linq query I’m using ‘db.webpages_Memberships’ as DbSet, so don’t forget to implement it. Here is the DbSet.

public class UsersContext : DbContext
{
    public UsersContext()
        : base("DefaultConnection")
    {
    }

    public DbSet<UserProfile> UserProfiles { get; set; }
    //newly added
    public DbSet<webpages_Membership> webpages_Memberships { get; set; }
}

I highlighted the changes made in above code. Notice I have used ‘webpages_Memberships’ as a model, we need to implement this too, this section is completely new.

[Table("webpages_Membership")]
public class webpages_Membership
{
    [Key]
    public int UserId { get; set; }
    public DateTime CreateDate { get; set; }
    public string ConfirmationToken { get; set; }
    public bool IsConfirmed { get; set; }
    public DateTime LastPasswordFailureDate { get; set; }
    public int PasswordFailuresSinceLastSuccess { get; set; }
    public string Password { get; set; }
    public DateTime PasswordChangeDate { get; set; }
    public string PasswordSalt { get; set; }
    public string PasswordVerificationToken { get; set; }
    public DateTime PasswordVerificationTokenExpirationDate { get; set; }
}

Also, the second linq query will return Boolean (variable name is ‘any’) value because I have used .Any() with linq query to check ‘do you have any matching record’, if yes return ‘true’ if no return ‘false’ and show the not matched message immediately.

In case both matches Boolean will have ‘true’ value. Then a random password of length 6 will be generated using a method, given below.

private string GenerateRandomPassword(int length)
{
    string allowedChars = "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ0123456789!@$?_-*&#+";
    char[] chars = new char[length];
    Random rd = new Random();
    for (int i = 0; i < length; i++)
    {
        chars[i] = allowedChars[rd.Next(0, allowedChars.Length)];
    }
    return new string(chars);
}

And this generated password will saved in membership database with the help of WebSecurity.ResetPassword method that accepts token and new password. At the same time will get the user’s email id using linq query and send this newly generated password.

Okay, I’m done with everything now, but?

I have used ‘SendEmail’ method twice and the code is here, which is for those who don’t know how to send email.

private void SendEMail(string emailid, string subject, string body)
{
    SmtpClient client = new SmtpClient();
    client.DeliveryMethod = SmtpDeliveryMethod.Network;
    client.EnableSsl = true;
    client.Host = "smtp.gmail.com";
    client.Port = 587;

    System.Net.NetworkCredential credentials = new System.Net.NetworkCredential("[email protected]", "xxxxx");
    client.UseDefaultCredentials = false;
    client.Credentials = credentials;

    MailMessage msg = new MailMessage();
    msg.From = new MailAddress("[email protected]");
    msg.To.Add(new MailAddress(emailid));

    msg.Subject = subject;
    msg.IsBodyHtml = true;
    msg.Body = body;

    client.Send(msg);
}

Hope this helps.

Comments

  1. One more detail - once the password is reset, you need to clear the PasswordVerificationToken from the Membership table. This will prevent unwanted resets, especially in the case of someone's mail getting hacked.

    ReplyDelete
    Replies
    1. agree. WebSecurity.ResetPassword(rt, newpassword) class is smart enough to do this automatically.

      Delete
  2. Good post. However a better solution to resetting the password is to send a password reset token and let the user reset the password. This will prevent unauthorised users resetting passwords of other users.

    ReplyDelete
    Replies
    1. I'm also sending the password reset token via mail, if you read this article.

      Delete
  3. Thank you, I was having trouble using the ResetPassword and this post has the answer that I need to use "token = WebSecurity.GeneratePasswordResetToken(..)" and pass that token in WebSecurity.ResetPassword.

    ReplyDelete
  4. Thanks for this article, very clear and direct.

    ReplyDelete
  5. Hi,nice work
    but i have an error when i test this.
    There is already an object named 'webpages_Membership' in the database.

    ReplyDelete
    Replies
    1. I also have the same problem...
      if I don't add the 'webpages_Membership' to the Context the projects doesn't builed. If I do I can migrate and If i start the project it erases all my data.

      Delete
    2. I figured it out:
      I stead of adding 'webpages_Membership' to the DAL folder it should be added in the AccountModels.cs

      Delete
  6. Thank you, I really appreciate your clear explanation of this topic.

    ReplyDelete
  7. Thanks,excellent Work,
    i have juste a question: Do we Need a vue ResetPassword.chtml??

    ReplyDelete
  8. Good article.
    Btw, do we have link to the solution we can download from?

    ReplyDelete
  9. Cannot implicitly convert type 'long' to 'int?'. An explicit conversion exists (are you missing a cast?)


    during the this method WebSecurity.ResetPassword(token, newpassword);

    any one tell solution or this

    ReplyDelete
  10. I am getting error "Invalid column name 'PasswordChangedDate'" after completing forgot password and reset password. But no error occurs after forgot password. after reset password mainly after creating table webpages_Membership this occurs. it detects error at the line.

    what to do now
    ?

    ReplyDelete
    Replies
    1. i'm getting the exact same thing. did you ever fix your problem? please help

      Delete
  11. Hi,

    Nice article. but i have a small doubt. Do we need registerpassowrd.cshtml file. If so kindly share the code.

    ReplyDelete
  12. Thanks for the great post man. I tried this out, but after receiving the New Password email, the program still recognizes the old password and not the new one that is generated randomly and sent with the email. Help

    ReplyDelete
    Replies
    1. Can you post your registerpassword.cshtml code?

      Delete
  13. Could you please post the registerpassword.cshtml file

    ReplyDelete
  14. Hi Abimanyu as i fallow ur entire process as same but i got error like this:The SMTP server requires a secure connection or the client was not authenticated. The server response was: 5.5.1 Authentication Required plz help me

    ReplyDelete
  15. I got a problem in SendEmail method is used error is does not exist in current text and another problem is emailId,Detils are invalid coloumns show in register action part plz tell me these two errors

    ReplyDelete
  16. RESET PASSWORD VIEW
    Create a view called ResetPassword - under the Account folder. I personally made it a partial view.
    If your using razor then just add @TempData["Message"] on the view
    Save the view and your done ...the return will then display your message

    ReplyDelete
  17. I used it but I am not using the membership provider. I am using my own. When executing "WebSecurity.GeneratePasswordResetToken(username)" It throws error. Please do help me out.

    ReplyDelete
  18. Brella from Brasil

    Thanks for the article. Very nice.

    ReplyDelete
  19. Thanks, was very helpful, but I did end up using WebSecurity.ResetPassword, because after the user clicks on the email link, I want them to be able to make a new password.

    ReplyDelete
  20. Thanks for the good Post... I have one doubt that in webpages_Membership table column name "PasswordVerificationToken" value is expired at what time ?
    means first time it reset the password and send the mail but again when i try to reset the password it throw the error so upto what time i need to wait to reset the password again.

    ReplyDelete
  21. My password reset token displays next to username and does not get sent to my email address?

    ReplyDelete
  22. great articile but can you provide a solution

    ReplyDelete
  23. Thank you very much! :)

    ReplyDelete
  24. Error 1 'webpages_Membership': member names cannot be the same as their enclosing type
    i have this error can anyone help me out please

    ReplyDelete
  25. reset password action method is not being called what to do

    ReplyDelete
  26. Please share for me your code. I try coding but it not run.

    ReplyDelete
  27. Thank you so much ! Great and helpful article

    ReplyDelete
  28. Can you provide link to download the source code?

    ReplyDelete
  29. Thank you. It was nice article. Helped me alot :)

    ReplyDelete
  30. Thanks brthr for this great article :-)
    I having an issue when it runs 'No account exists for "UserName", sometimes error didn't get........
    what to do ???

    ReplyDelete
  31. For those who want to send reset link to emailid refere "https://www.google.com/settings/security/lesssecureapps" and turn on the access for less secure apps(i.e your application). and make changes in "SendEMail" method as follow:-

    private void SendEMail(string emailid, string subject, string body)
    {
    SmtpClient client = new SmtpClient();
    client.DeliveryMethod = SmtpDeliveryMethod.Network;
    client.EnableSsl = true;
    client.Host = "smtp.gmail.com";
    client.Port = 587;

    System.Net.NetworkCredential credentials = new System.Net.NetworkCredential("youremailid", "yourpassword");
    client.UseDefaultCredentials = false;
    client.Credentials = credentials;

    MailMessage msg = new MailMessage();
    msg.From = new MailAddress("youremailid");
    msg.To.Add(new MailAddress(emailid));

    msg.Subject = subject;
    msg.IsBodyHtml = true;
    msg.Body = body;

    client.Send(msg);
    }

    ReplyDelete
  32. very helpful article.
    i need WebSecurity.ResetPassword class ?

    ReplyDelete

Post a Comment

Popular posts from this blog

Migrating database from ASP.NET Identity to ASP.NET Core Identity

Customize User's Profile in ASP.NET Identity System