Part 3 - Implement forgot password functionality in asp.net MVC



Introduction

Today in the series Complete login and registration system in ASP.NET MVC application, we will learn to implement Forgot Password functionality.

This is the 3rd part of this series So before continuing with this article, I would recommend you to read the previous article of this series where I have shown you how to verify registration link and create a login page with Remember Me option if you have not read the article till now. 

In this series, till now we have learned to create registration page with email verification and creating a login page with Remember Me option. Here in this article, we will learn to create forgot password page in our asp.net MVC application step by step. Here we will do followings steps...


  1. First, we will create a page where we will ask user to provide their email id
  2. Second, we will verify user account with the provided email id and we will send reset password link to users email id. 
  3. Third, we will have a function for verifying the reset password link and if the link is valid then we will provide a page to the user for reset their password.
  4. Finally, we will update the new password in the database. 

Let's start implementing Forgot Password functionality in asp.net MVC application. 

As this is the 3rd part of this series, we will start from what we have done till now in part 2. You can download the source code of the part 2 application if you haven't downloaded yet.

After downloading the application we will open the application in Visual Studio 2015.

Let's start implementing forgot password functionality in asp.net MVC application.

Step-1: Add a new MVC action in UserController.

In the first step, we will create a new MVC action in our UserController where we will ask users to provide their email id for getting forgot password link. 

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

Step-2: Add view for the ForgotPassword action.

ForgotPassword.cshtml
@{
    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">
            <label class="control-label col-md-2">Email ID</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>
}

Update SendVerificationLinkEmail function

In the next step, we will send a reset password email to user email id and for this, we can use the function SendVerificationLinkEmail what we had used to sent account verification email. We have to just update few lines of code to reuse the SendVerificationLinkEmail function for sending Reset Password email also. Here you can see yellow mark lines we have updated.  
[NonAction]
public void SendVerificationLinkEmail(string emailID, string activationCode, string emailFor = "VerifyAccount")
{
    var verifyUrl = "/User/"+emailFor+"/" + activationCode;
    var link = Request.Url.AbsoluteUri.Replace(Request.Url.PathAndQuery, verifyUrl);

    var fromEmail = new MailAddress("dotnetawesome@gmail.com", "Dotnet Awesome");
    var toEmail = new MailAddress(emailID);
    var fromEmailPassword = "******"; // Replace with actual password

    string subject = "";
    string body = "";
    if (emailFor == "VerifyAccount")
    {
        subject = "Your account is successfully created!";
        body = "<br/><br/>We are excited to tell you that your Dotnet Awesome account is" +
            " successfully created. Please click on the below link to verify your account" +
            " <br/><br/><a href='" + link + "'>" + link + "</a> ";

    }
    else if (emailFor == "ResetPassword")
    {
        subject = "Reset Password";
        body = "Hi,<br/>br/>We got request for reset your account password. Please click on the below link to reset your password" +
            "<br/><br/><a href="+link+">Reset Password link</a>";
    }


    var smtp = new SmtpClient
    {
        Host = "smtp.gmail.com",
        Port = 587,
        EnableSsl = true,
        DeliveryMethod = SmtpDeliveryMethod.Network,
        UseDefaultCredentials = false,
        Credentials = new NetworkCredential(fromEmail.Address, fromEmailPassword)
    };

    using (var message = new MailMessage(fromEmail, toEmail)
    {
        Subject = subject,
        Body = body,
        IsBodyHtml = true
    })
    smtp.Send(message);
}

You can see the above function, I have added an additional parameter "emailFor" with default value "VerifyAccount" for identifying what email is the email for. According to the emailFor parameter value, email content will be different.  

Step-3: Add another action in our UserController for verifying the email id. 

Now we have to add another action in our UserController for verifying the email id provided in the forgot password page and if found valid email id, we will send reset password link to user email id.

[HttpPost]
public ActionResult ForgotPassword(string EmailID)
{
    //Verify Email ID
    //Generate Reset password link 
    //Send Email 
    string message = "";
    bool status = false;

    using (MyDatabaseEntities dc = new MyDatabaseEntities())
    {
        var account = dc.Users.Where(a => a.EmailID == EmailID).FirstOrDefault();
        if (account != null)
        {
            //Send email for reset password
            string resetCode = Guid.NewGuid().ToString();
            SendVerificationLinkEmail(account.EmailID, resetCode, "ResetPassword");
            account.ResetPasswordCode = resetCode;
            //This line I have added here to avoid confirm password not match issue , as we had added a confirm password property 
            //in our model class in part 1
            dc.Configuration.ValidateOnSaveEnabled = false;
            dc.SaveChanges();
            message = "Reset password link has been sent to your email id.";
        }
        else
        {
            message = "Account not found";
        }
    }
    ViewBag.Message = message;
    return View();
}

You can see here in line 11, we have generated a unique identification no (GUID) for sending with the Reset Password email. This Unique identification no we will store in our user table so we can use this unique no to find the user account associated with it. Ok.

Step-4: Update User table from the database.

We sent Reset password link to user email id with a unique identification no in the previous step. This unique identification no we will store in the User table in our database so we can find the user account with this unique id. That's why we have to add a new column (ResetPasswordCode) to our User table. See the below image.


Step-5: Update Entity Model.

As we have updated our database table structure, we have to update our entity model also.

Open MyModel.edmx by double-clicking from Solution Explorer > Models. Right-click anywhere in the empty area > click on Update Model from Database from the context menu > Save.

Step-6: Add a ViewModel.

In the next step 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. And for this, we will add a ViewModel in our application first.

Go to Solution Explorer > Models folder > Right-click on the Models folder > Add > Class... > Enter class name "ResetPasswordModel.cs" > Click on Add button.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;

namespace RegistrationAndLogin.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; }
    }
}

Step-7: Add a new MVC Action for verifying ResetPassword link.

Now, we have to verify the reset password link right? So for this, we have to add an another MVC Action here in the UserController.

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 (MyDatabaseEntities dc = new MyDatabaseEntities())
    {
        var user = dc.Users.Where(a => a.ResetPasswordCode == id).FirstOrDefault();
        if (user != null)
        {
            ResetPasswordModel model = new ResetPasswordModel();
            model.ResetCode = id;
            return View(model);
        }
        else
        {
            return HttpNotFound();
        }
    }
}

Here we have verified the link from the unique identification no what we sent to the Reset Password email link. The unique identification no we will get here in the id parameter.
If we will find a valid account associated with this unique no then we will return a view with the ViewModel after setting the ResetCode. So the user can view the page from where they can reset their password.   

Step-8: Add view for the ResetPasswod action of UserController.

ResetPassword.cshtml
@model RegistrationAndLogin.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="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}
<div>
    @Html.ActionLink("Back to List", "Index")
</div>
@section Scripts{
    <script src="~/Scripts/jquery.validate.min.js"></script>
    <script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
}

Step-9: Add an another MVC action here in the UserController for Reset the password.

Now we have to add an another MVC action here in the UserController for update the password right?

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ResetPassword(ResetPasswordModel model)
{
    var message = "";
    if (ModelState.IsValid)
    {
        using (MyDatabaseEntities dc = new MyDatabaseEntities())
        {
            var user = dc.Users.Where(a => a.ResetPasswordCode == model.ResetCode).FirstOrDefault();
            if (user != null)
            {
                user.Password = Crypto.Hash(model.NewPassword);
                user.ResetPasswordCode = "";
                dc.Configuration.ValidateOnSaveEnabled = false;
                dc.SaveChanges();
                message = "New password updated successfully";
            }
        }
    }
    else
    {
        message = "Something invalid";
    }
    ViewBag.Message = message;
    return View(model);
}
See in line 11, we make the ResetPasswordCode empty for invalidate the reset password link. So the reset password link cannot be used again for resetting password once it is used. 

Step-10: Run Application.

We have done all the steps. Now it's time to run the application but before run the application don't forget to update your outgoing email password in the SendVerificationLinkEmail function as we have marked it as ***** till now.  

Hello ! My name is Sourav Mondal. I am a software developer working in Microsoft .NET technologies since 2010.

I like to share my working experience, research and knowledge through my site.

I love developing applications in Microsoft Technologies including Asp.Net webforms, mvc, winforms, c#.net, sql server, entity framework, Ajax, Jquery, web api, web service and more.