(C# ASP.NET Core) Reset Password functionality in Identity based Authentication

We have seen in the previous tutorial that when a user fills the forgot password form, then he is emailed a link to the reset password page. The link contains a password reset code. Now, in this tutorial we shall add a page for reset password.
(Rev. 19-Mar-2024)

Categories | About |     |  

Parveen,

Table of Contents (top down ↓)

How does it work

So, how does it work?

A ResetPassword page presents a form that obtains email, new password and a confirm password from the user. This form also contains a hidden field for the password reset code.

These three items - email, password and reset code - are passed to the ResetPasswordAsync function of the UserManager class.

If you are following the course, then the complete source code for this tutorial has been provided in your downloads section.

Video Explanation (see it happen!)

Please watch the following youtube video:

Add ResetPassword Page

Let us add the ResetPassword page now!

Open the solution explorer and locate the auth area. Right click the Pages folder to add a page called ResetPassword.

Open the markup file ResetPassword.cshtml. Let us examine the markup.

This is the markup for the ResetPassword.cshtml file.


// Areas -> Auth -> Pages -> ResetPassword.cshtml 

@page

@model MyRazorApp.Areas.Auth.Pages.ResetPasswordModel

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<h1>Reset Password</h1>

<div asp-validation-summary="All"></div>

<form method="post">

  <table>

    <tr>
      <td>
        <label asp-for="UserID"></label>
      </td>
      <td>
        <input asp-for="UserID" />
      </td>
      <td>
        <span asp-validation-for="UserID"></span>
      </td>
    </tr>

    <tr>
      <td>
        <label asp-for="UserPassword"></label>
      </td>
      <td>
        <input asp-for="UserPassword" />
      </td>
      <td>
        <span asp-validation-for="UserPassword"></span>
      </td>
    </tr>

    <tr>
      <td>
        <label asp-for="ConfirmPassword"></label>
      </td>
      <td>
        <input asp-for="ConfirmPassword" />
      </td>
      <td>
        <span asp-validation-for="ConfirmPassword"></span>
      </td>
    </tr>

    <tr>

      <td colspan="2" style="text-align:right">

        <input asp-for="Code" type="hidden" />

        <input type="submit" value="Reset!" />

      </td>

    </tr>
  </table>

</form>

  

First we have the directives for page, model and addTagHelper.

Then, we have a tag for presenting a validation summary.

An html table has been used to create the form for resetting the password.

The first tr row is for obtaining the email of the user. The input textbox is connected to a bind property called UserID which contains the email of the user.

The next rows are for password and confirm password input textboxes.

The last row contains the submit button and a hidden field for the password reset code. This hidden field is collected in the OnGet method when the user reaches this page and brings it from the link that he clicked in his email.

Next let us have a look at the backing class. Open the solution explorer and double click to open the ResetPassword.cshtml.cs file.

This is the ResetPassword.cshtml.cs file.

// Areas -> Auth -> Pages -> ResetPassword.cshtml 

// namespaces 
using Microsoft.AspNetCore.Authorization;

using Microsoft.AspNetCore.Identity;

using Microsoft.AspNetCore.Mvc;

using Microsoft.AspNetCore.Mvc.RazorPages;

using Microsoft.AspNetCore.WebUtilities;

using System.ComponentModel.DataAnnotations;

using System.Text;

namespace MyRazorApp.Areas.Auth.Pages
{

  [AllowAnonymous]
  public class ResetPasswordModel : PageModel
  {

    // email (UserID) 
    [BindProperty]
    [Required]
    [DataType(DataType.EmailAddress)]
    public String? UserID { get; set; }

    // password 
    [BindProperty]
    [Required]
    [DataType(DataType.Password)]
    public String? UserPassword { get; set; }

    // confirm password 
    [BindProperty]
    [Required]
    [Compare("UserPassword", ErrorMessage = "Passwords do not match")]
    [DataType(DataType.Password)]
    public string? ConfirmPassword { get; set; }

    // password reset code 
    [BindProperty]
    public string? Code { get; set; }
    private readonly UserManager<IdentityUser> _um;

    public ResetPasswordModel(UserManager<IdentityUser> um)
    {

      _um = um;

    }

    public IActionResult OnGet(string? code = null)
    {

      if (code == null)
      {

        return BadRequest("code not provided");

      }

      else 
      {

        Code = code;

        return Page();

      }

    }

    public async Task<IActionResult> OnPostAsync()
    {

      if (!ModelState.IsValid)
      {

        return Page();

      }

      // decode back the base64 token 
      String token = Encoding.UTF8.GetString(
          WebEncoders.Base64UrlDecode(Code ?? String.Empty));

      IdentityUser user = await _um.FindByEmailAsync(UserID);

      var result = await _um.ResetPasswordAsync(user, token, UserPassword);

      if (!result.Succeeded)
      {

        foreach (var error in result.Errors)
        {

          ModelState.AddModelError(string.Empty, error.Description);

        }

      }

      else 
      {

        // using validator for displaying message (only for tutorial case) 
        // use a message property, or redirect to login page in real project 
        ModelState.AddModelError(string.Empty,
              "Password changed! (Redirect in real case) ");

      }

      return Page();

    }

  }

}

First we have the namespace directives.

The class has been marked as [AllowAnonymous] for obvious reasons.

Then we have the bind property for email of the user.

After that we have the bind properties for the password and reset password fields.

Then we have a bind property for password reset Code.

Constructor based dependency injection has been used to obtain the UserManager service. We have cached the instance as a readonly member.

Next we have the OnGet method that executes when the user reaches this page by clicking the link for reset password. Recall that the link contains the password reset token. The parameter is now collected from the OnGet method. It sets the Code property that binds to a hidden field in the form. The same value comes back when the user posts the form.

Next we have the OnPostAsync function. This function executes when the user submits and posts the form.

The password reset token is decoded back.

The email of the user is used to obtain the IdentityUser instance.

The ResetPasswordAsync method of the UserManager is used to change the password.

If the function fails then the errors are presented as validation summary errors.

If the function succeeds, the user can be redirected to the login page and informed accordingly. But in this tutorial we have used the validation summary to present the success message.

Test the Project

Run the project and reach the login page and click the Forgot Password link. We are asked to enter our email. Enter a registered email, and submit the form. We reach the reset password page. We have reached this page directly because we are running a tutorial. However, in a real project the user reaches this page by clicking a link that is emailed to the user.

Enter the email, and also enter the new password. Submit the form. We verify that the password is changed successfully!


This Blog Post/Article "(C# ASP.NET Core) Reset Password functionality in Identity based Authentication" by Parveen is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.