In one of my previous post, I have explained about how to create simple login and registration form in asp.net mvc, now in this post I have explained, how we can create forgot password functionality to send your reset password link by email in ASP.NET MVC.

You may also like to read : Encrypt password & decrypt it (C# Console application example)

So here are the steps, which we will follow to reset user password.

  1. Create a forgot password link and when user clicks on it, take it to another form where we ask user to enter registered email id.
  2. If Email exists, send a link with unique Id to user's email address and ask them to reset password using this unique link.
  3. On Clicking unique link, take user to new page to set new password.
  4. Save the new password in the database, after verifying that unique link contains our unique reset password code.

Let's begin by creating a new column in our database table to save unique code of reset password link

Now, update your .edmx file to get newly added column (I am using database first approach here)

We need to create a view for forgot password, so go to your AccountController.cs file and add the C# code as show below

public ActionResult ForgotPassword()
{
     return View(); 
}

Add the view for Forgot password page and it's .cshtml code as show below, to ask user for registered email id

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

<h2>Forgot Password</h2>

@using (Html.BeginForm())
{
<div class="form-horizontal">
    <hr />
    <div class="text-success">
        @ViewBag.Message
    </div>
    <div class="form-group">
        Please enter your registered email address below to receive an email containing a link, to reset your password.
    </div>
    <div class="form-group">
        <label class="control-label col-md-2">Email Address</label>
        @Html.TextBox("EmailID", "", new { @class = "form-control" })
    </div>
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" value="Submit" class="btn btn-success" />
        </div>
    </div>
</div>
}

Now, we need to create code, to get User email, check if user exists in database, if yes, send email to him/her with reset password link, so in your AccountController.cs

    [HttpPost]
        public ActionResult ForgotPassword(string EmailID)
        {
            string resetCode = Guid.NewGuid().ToString();
            var verifyUrl = "/Account/ResetPassword/" + resetCode;
            var link = Request.Url.AbsoluteUri.Replace(Request.Url.PathAndQuery, verifyUrl);
            using (var context = new LoginRegistrationInMVCEntities())
            {
                var getUser = (from s in context.RegisterUsers where s.Email == EmailID select s).FirstOrDefault();
                if (getUser != null)
                {
                    getUser.ResetPasswordCode = resetCode;

                    //This line I have added here to avoid confirm password not match issue , as we had added a confirm password property 

                    context.Configuration.ValidateOnSaveEnabled = false;
                    context.SaveChanges();

                    var subject = "Password Reset Request";
                    var body = "Hi " + getUser.FirstName + ", <br/> You recently requested to reset your password for your account. Click the link below to reset it. " +

                         " <br/><br/><a href='" + link + "'>" + link + "</a> <br/><br/>" +
                         "If you did not request a password reset, please ignore this email or reply to let us know.<br/><br/> Thank you";

                    SendEmail(getUser.Email, body, subject);

                    ViewBag.Message = "Reset password link has been sent to your email id.";
                }
                else
                {
                    ViewBag.Message = "User doesn't exists.";
                    return View();
                }
            }

            return View();
        }

        private void SendEmail(string emailAddress, string body, string subject)
        {
            using (MailMessage mm = new MailMessage("youremail@gmail.com", emailAddress))
            {
                mm.Subject = subject;
                mm.Body = body;

                mm.IsBodyHtml = true;
                SmtpClient smtp = new SmtpClient();
                smtp.Host = "smtp.gmail.com";
                smtp.EnableSsl = true;
                NetworkCredential NetworkCred = new NetworkCredential("youremail@gmail.com", "YourPassword");
                smtp.UseDefaultCredentials = true;
                smtp.Credentials = NetworkCred;
                smtp.Port = 587;
                smtp.Send(mm);

            }
        }

Now, we will verify the reset password link and if we found a valid link then we will provide a page to the user, from where the user can reset their password, but before we create view for Reset password ActionResult, we would need to create ViewModel for it

So, right click on your Models Folder and add new Class "ResetPasswordModel.cs"

using System.ComponentModel.DataAnnotations;


namespace LoginRegistrationInMVCWithDatabase.Models
{
    public class ResetPasswordModel
    {
        [Required(ErrorMessage = "New password required", AllowEmptyStrings = false)]
        [DataType(DataType.Password)]
        public string NewPassword { get; set; }

        [DataType(DataType.Password)]
        [Compare("NewPassword", ErrorMessage = "New password and confirm password does not match")]
        public string ConfirmPassword { get; set; }

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

Now, create a ActionResult to show valid Reset password view, if the reset password code is correct

public ActionResult ResetPassword(string id)
        {
            //Verify the reset password link
            //Find account associated with this link
            //redirect to reset password page
            if (string.IsNullOrWhiteSpace(id))
            {
                return HttpNotFound();
            }

            using (var context = new LoginRegistrationInMVCEntities())
            {
                var user = context.RegisterUsers.Where(a => a.ResetPasswordCode == id).FirstOrDefault();
                if (user != null)
                {
                    ResetPasswordModel model = new ResetPasswordModel();
                    model.ResetCode = id;
                    return View(model);
                }
                else
                {
                    return HttpNotFound();
                }
            }
        }

Add the view and code for the above view

@model LoginRegistrationInMVCWithDatabase.Models.ResetPasswordModel
@{
    ViewBag.Title = "Reset Password";
}
<h2>Reset Password</h2>
@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
    <div class="form-horizontal">
        <h4>Reset Password</h4>
        <hr />
        <div class="text-danger">
            @ViewBag.Message
        </div>
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        <div class="form-group">
            @Html.LabelFor(model => model.NewPassword, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.NewPassword, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.NewPassword, "", new { @class = "text-danger" })
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.ConfirmPassword, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.ConfirmPassword, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.ConfirmPassword, "", new { @class = "text-danger" })
            </div>
        </div>
        <div class="form-group">
            @Html.HiddenFor(a => a.ResetCode)
        </div>
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
}
@section Scripts{
    <script src="~/Scripts/jquery.validate.min.js"></script>
    <script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
}

Once, user enters the new password and click's "Save", we need to save the updated password in the database, so let's create the C# to read and save it in the AccountController.cs

[HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult ResetPassword(ResetPasswordModel model)
        {
            var message = "";
            if (ModelState.IsValid)
            {
                using (var context = new LoginRegistrationInMVCEntities())
                {
                    var user = context.RegisterUsers.Where(a => a.ResetPasswordCode == model.ResetCode).FirstOrDefault();
                    if (user != null)
                    {
                        //you can encrypt password here, we are not doing it
                        user.Password = model.NewPassword;
                        //make resetpasswordcode empty string now
                        user.ResetPasswordCode = "";
                        //to avoid validation issues, disable it
                        context.Configuration.ValidateOnSaveEnabled = false;
                        context.SaveChanges();
                        message = "New password updated successfully";
                    }
                }
            }
            else
            {
                message = "Something invalid";
            }
            ViewBag.Message = message;
            return View(model);
        }

That's it, we are done, it's time to run the application but before run the application don't forget to update your outgoing email and password in the SendEmail function, you can also read about sending email in asp.net mvc with attachments.

Also, as stated in the comments of the last code section, I am not saving encrypted password, but it is good practice to save password encrypted in database.