Creating Password Reset feature in ASP.NET Core Identity

Creating Password Reset feature in ASP.NET Core Identity

When a user forgets his/her password then he needs the option to reset it. In ASP.NET Core Identity you can create the Password Reset feature which comes out to be handy in such situations.

How Reset Password Feature works in Identity

Suppose a user forgot his/her password then he/she can go to the Forgot Password Page. This page screenshot is given below:

forgot password page identity

From this page he/she can request an email which will contain a link to Reset his/her Password. When the user clicks on this link then he/she is taken to the Reset Password Page and from here he/she can create a new password to their account. This screenshot of reset password page is given below:

reset password page identity
When working with Identity you should also know the process to Perform Email Confirmation of Users in ASP.NET Core Identity

Enabling Token Generation

First add the AddDefaultTokenProviders extension method to enable the token generation in the project. Add this inside the ConfigureServices method of Startup.cs class as shown below:

services.AddIdentity<AppUser, IdentityRole>().AddEntityFrameworkStores<AppIdentityDbContext>().AddDefaultTokenProviders();

If you want then you can set the time of token validity by adding the below code to the ConfigureServices method of Startup.cs class.

services.Configure<DataProtectionTokenProviderOptions>(opts => opts.TokenLifespan = TimeSpan.FromHours(10));

Here I have set the token validity to 10 hours.

Creating Forgot Password Feature

I am creating the ForgotPassword action in AccountController. It may be different in your case. Anyway add ForgotPassword actions in this controller. It’s work would be to send the email, to the user’s email id, with a link to reset the password.

I have also added an action by the name of ForgotPasswordConfirmation whose work is to show the confirmation message telling users that the reset email is sent successfully, and now they need to check their inbox for the email.

The codes of these actions are highlighted:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Identity.Models;
using System.Threading.Tasks;
using System.Security.Claims;
using Identity.Email;
using System.ComponentModel.DataAnnotations;

namespace Identity.Controllers
{
    [Authorize]
    public class AccountController : Controller
    {
        private UserManager<AppUser> userManager;
        private SignInManager<AppUser> signInManager;

        public AccountController(UserManager<AppUser> userMgr, SignInManager<AppUser> signinMgr)
        {
            userManager = userMgr;
            signInManager = signinMgr;
        }

        // other codes

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

        [HttpPost]
        [AllowAnonymous]
        public async Task<IActionResult> ForgotPassword([Required]string email)
        {
            if (!ModelState.IsValid)
                return View(email);

            var user = await userManager.FindByEmailAsync(email);
            if (user == null)
                return RedirectToAction(nameof(ForgotPasswordConfirmation));

            var token = await userManager.GeneratePasswordResetTokenAsync(user);
            var link = Url.Action("ResetPassword", "Account", new { token, email = user.Email }, Request.Scheme);

            EmailHelper emailHelper = new EmailHelper();
            bool emailResponse = emailHelper.SendEmailPasswordReset(user.Email, link);

            if (emailResponse)
                return RedirectToAction("ForgotPasswordConfirmation");
            else
            {
                // log email failed 
            }
            return View(email);
        }

        [AllowAnonymous]
        public IActionResult ForgotPasswordConfirmation()
        {
            return View();
        }
    }
}

Explanation: Only the below 4 lines are important to note. These are given below:

var token = await userManager.GeneratePasswordResetTokenAsync(user);
var link = Url.Action("ResetPassword", "Account", new { token, email = user.Email }, Request.Scheme);

EmailHelper emailHelper = new EmailHelper();
bool emailResponse = emailHelper.SendEmailPasswordReset(user.Email, link);

I created a password reset token from the GeneratePasswordResetTokenAsync method of the UserManager<T> class. Next I added this token to a link. This link will be served as a reset password link.

Then I called the method called SendEmailPasswordReset() of EmailHelper class to send the email to the user.

I created the EmailHelper class inside the Email folder that resided on the root of the project and has the following code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Mail;
using System.Threading.Tasks;

namespace Identity.Email
{
    public class EmailHelper
    {
        public bool SendEmailPasswordReset(string userEmail, string link)
        {
            MailMessage mailMessage = new MailMessage();
            mailMessage.From = new MailAddress("[email protected]");
            mailMessage.To.Add(new MailAddress(userEmail));

            mailMessage.Subject = "Password Reset";
            mailMessage.IsBodyHtml = true;
            mailMessage.Body = link;

            SmtpClient client = new SmtpClient();
            client.Credentials = new System.Net.NetworkCredential("[email protected]", "yourpassword");
            client.Host = "smtpout.secureserver.net";
            client.Port = 80;

            try
            {
                client.Send(mailMessage);
                return true;
            }
            catch (Exception ex)
            {
                // log exception
            }
            return false;
        }
    }
}

The work of this class is to send the email using SMTP.

I will also need to add 2 Views called ForgotPassword & ForgotPasswordConfirmation inside the Views ➤ Account folder. Their codes are given below:

1. “ForgotPassword” view

@model string

@{
    ViewData["Title"] = "Forgot Password";
}

<h1 class="bg-info text-white">Forgot Password</h1>

<form asp-action="ForgotPassword" method="post">
    <div class="form-group">
        <label>Email</label>
        <input name="email" class="form-control" />
    </div>
    <button class="btn btn-primary" type="submit">Submit</button>
</form>

2. “ForgotPasswordConfirmation” view

@{
    ViewData["Title"] = "Forgot Password Confirmation";
}

<h1>Forgot Password Confirmation</h1>
<p>
    The email has been sent. Please check your email to reset your password.
</p>

Testing Forgot Password functionality

Run your application and go to the forgot password url and fill your email address and click the submit button to send the password reset email. See the below image.

forgot password page identity

Next you will get confirmation message. See below image.

password confirmation message

Now check the password reset email in your inbox and click it’s link. I have shown this email in the below image.

password reset email identity

Now it’s time to allow the user to create his/her new password.

Create New Password Feature

When the user clicks the link on the password reset email then he/she is taken to the page where he/she can create the new password.

The link directs users to the ResetPassword action of Account controller. So add ResetPassword action along with another action called ResetPasswordConfirmation to the Account controller. Their codes are given below.

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Identity.Models;
using System.Threading.Tasks;
using System.Security.Claims;
using Identity.Email;
using System.ComponentModel.DataAnnotations;

namespace Identity.Controllers
{
    [Authorize]
    public class AccountController : Controller
    {
        private UserManager<AppUser> userManager;
        private SignInManager<AppUser> signInManager;

        public AccountController(UserManager<AppUser> userMgr, SignInManager<AppUser> signinMgr)
        {
            userManager = userMgr;
            signInManager = signinMgr;
        }

        // other codes

        [AllowAnonymous]
        public IActionResult ResetPassword(string token, string email)
        {
            var model = new ResetPassword { Token = token, Email = email };
            return View(model);
        }

        [HttpPost]
        [AllowAnonymous]
        public async Task<IActionResult> ResetPassword(ResetPassword resetPassword)
        {
            if (!ModelState.IsValid)
                return View(resetPassword);

            var user = await userManager.FindByEmailAsync(resetPassword.Email);
            if (user == null)
                RedirectToAction("ResetPasswordConfirmation");

            var resetPassResult = await userManager.ResetPasswordAsync(user, resetPassword.Token, resetPassword.Password);
            if (!resetPassResult.Succeeded)
            {
                foreach (var error in resetPassResult.Errors)
                    ModelState.AddModelError(error.Code, error.Description);
                return View();
            }

            return RedirectToAction("ResetPasswordConfirmation");
        }

        public IActionResult ResetPasswordConfirmation()
        {
            return View();
        }
    }
} 

Explanation: The HTTP GET version of ResetPassword action method gets called when the user clicks on the reset password link on his/her email. The work of this action method is to grab the token and email address and bind them to the view.

Next, the user adds the new password and confirm password on the View (i.e. ResetPassword.cshtml) and click the button to save it. This calls the HTTP POST version of ResetPassword action method. This action method gets new password, user email and token through the ResetPassword model calls which is provided in it’s parameter.

The code that creates the new password of the user is:

var resetPassResult = await userManager.ResetPasswordAsync(user, resetPassword.Token, resetPassword.Password);

Next, I need to create the ResetPassword.cs class inside the Models folder with the following code.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

namespace Identity.Models
{
    public class ResetPassword
    {
        [Required]
        public string Password { get; set; }

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

        public string Email { get; set; }
        public string Token { get; set; }
    }
}

I will also need to create 2 views called ResetPassword & ResetPasswordConfirmation inside the Views ➤ Account folder with the following codes:

1. ResetPassword.cshtml

@model ResetPassword

@{
    ViewData["Title"] = "Reset Password";
}

<h1 class="bg-info text-white">Reset Password</h1>
<div class="text-danger" asp-validation-summary="All"></div>

<form asp-action="ResetPassword" method="post">
    <div class="form-group">
        <label asp-for="Password"></label>
        <input asp-for="Password" class="form-control" />
    </div>
    <div class="form-group">
        <label asp-for="ConfirmPassword"></label>
        <input asp-for="ConfirmPassword" class="form-control" />
    </div>
    <input type="hidden" asp-for="Email" class="form-control" />
    <input type="hidden" asp-for="Token" class="form-control" />
    <button class="btn btn-primary" type="submit">Submit</button>
</form>

2. ResetPasswordConfirmation.cshtml

@{
    ViewData["Title"] = "Reset Password Confirmation";
}

<h1>Reset Password Confirmation</h1>

<p>
    Password has been reset. Please <a asp-action="Login">Login</a>
</p>

The ResetPassword view will be the one from where the user creates his/her new password while ResetPasswordConfirmation will show the confirmation message to the user once his/her password gets changed successfully.

Testing Create New Password Feature

First I go to the password forgot page and ask for the password reset email. Then I click on the reset password link given on the email which is send to my inbox.

I reach the Reset Password page where I add my new password to the 2 input controls and click the Submit button. See the below image.

reset password page

Check the url of this page which in my case is:

https://localhost:44339/Account/ResetPassword?token=CfDJ8IEcMEvn3TFNtwyqP1DDIY4nTClx%2BenhaMoFHCu3zl60nhFZG6SgbSat36BA7l%2FU%2F0UrhG2CBvXC%2BFM8cPXF0KWuqwpg2XEKA7QRzfnFbi1VHDFtxa2ZURrliBkQ%2FzStRskkuSnIpc%2F3m%2Fy6T4R48Nhp4%2FoTqnnaX12S9RVvSZMVw%2FtpKI1fb%2B1ibsAm1cSVCiOZj54wGQwHmjfpYNDoQiLMlTOi8Az%2BfboqoX4W5oKB&[email protected]

Notice the token and email address are added to the query strings.

Finally I get the message that my Password has been reset. See the below image.

reset password confirmation identity

You can download the full codes of this tutorial from the below link:

Download

Conclusion

In this tutorial you learned.

  • Implementing forgot password functionality.
  • Registering token providers and to set up the life span of a token./li>
  • Implementing Reset Password functionality.

Hope you enjoyed learning these things.

SHARE THIS ARTICLE

  • linkedin
  • reddit
yogihosting

ABOUT THE AUTHOR

I hope you enjoyed reading this tutorial. If it helped you then consider buying a cup of coffee for me. This will help me in writing more such good tutorials for the readers. Thank you. Buy Me A Coffee donate

Leave a Reply

Your email address will not be published. Required fields are marked *