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

Very often a user is unable to remember the password that he created for a website. In such circumstances, the forgot password link comes to his rescue - it is one of the most important links on a login form. And, in this tutorial we shall implement the functionality for a forgot password link. Let's proceed!

Categories | About |     |  

Parveen,

How does it work

So, how does it work?

When a user clicks the forgot password link, he is taken to a page where he is asked to enter his email. Then this email is used to generate a password reset token.

And for security purposes, a link containing the token is emailed to the user. The link points to a reset password page - where most of the work happens, but will be added in the next tutorial.

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 the ForgotPassword Page

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

Let us examine the markup file ForgotPassword.cshtml. This page has to present a form that obtains email from the user.

// Areas -> Auth -> Pages -> ForgotPassword.cshtml 
@page

@model MyRazorApp.Areas.Auth.Pages.ForgotPasswordModel

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<h1>Please type EMail to Reset Password</h1>

<div style="color:red" 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 colspan="2" style="text-align:right">
        <input type="submit" value="Send!" />
      </td>
    </tr>

  </table>

</form>

  

First we have the directives for page, model and addTagHelper. Then we have a tag to present a ValidationSummary of the model errors.

An html table has been used to create a form. The first tr row is for the email input textbox. This input is connected to a backing property called UserID.

Lastly, we have the submit button for posting the ForgotPassword form.

Next let us examine the backing class. Open the solution explorer and double click to open the file ForgotPassword.cshtml.cs.

This is the code for the ForgotPasswordModel backing class.

// Areas -> Auth -> Pages > ForgotPassword.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 ForgotPasswordModel : PageModel
  {

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

    // UserManager service 
    private readonly UserManager<IdentityUser> _um;

    // generate password reset token 
    public ForgotPasswordModel(UserManager<IdentityUser> um)
    {

      _um = um;

    }

    public async Task<IActionResult> OnPostAsync()
    {

      if (!ModelState.IsValid)
      {

        return Page();

      }

      IdentityUser user = await _um.FindByEmailAsync(UserID);

      // if user not registered, or bad entry 
      if (
          (null == user)
            ||
          !(await _um.IsEmailConfirmedAsync(user))
      )
      {

        ModelState.AddModelError(string.Empty, "Please check email. Retry.");

        return Page();

      }

      var code = await _um.GeneratePasswordResetTokenAsync(user);

      // encode as base64 
      code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));

      // create the link and email or send by sms 
      // /* 
      // String link = Url.Page( 
      //                    "/ResetPassword", 
      //                    null, 
      //                    new { area = "Auth", code = code }, 
      //                    Request.Scheme); 

      // ModelState.AddModelError(string.Empty, "We have emailed a link."); 

      // return Page(); 

      // */ 

      // during testing - we redirect the user straightway 
      return RedirectToPage("ResetPassword", new { area = "auth", code });

    }

  }

}

First we have the namespace directives.

Then we have the ForgotPasswordModel class. The class has been marked as [AllowAnonymous] so that it is publically available to a user who has no means to login into our website.

UserID is the bind property that is connected to the input textbox for the email, and contains the email entered by the user.

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

The OnPost method executes when the user submits and posts the form.

The email is used to query the IdentityUser for this email id. If the user cannot be found, or if the user can be found but he hasn't yet confirmed his email, then a suitable failure message is added and presented as a validation failure.

Otherwise, a password reset token is generated.

The token is then encoded as a base64 string.

In a real application, we would use the Url.Page method to create a link to the ResetPassword page and email it to the user. Here we have commented this part because we are doing a tutorial.

For the sake of this tutorial we shall immediately redirect the user to ResetPassword page.

The ResetPassword page would be added in the next tutorial.

Run the Project so far

Let us test what we have done so far. Open the solution explorer, and run the project to open the login page. Locate the ForgotPassword link and click it. That brings us to a form for resetting the password. Enter an email that has not been registered yet. Submit the form. We verify that we are indeed shown a failure message.

Next enter a verified email. Submit the form. In this case we verify that we are redirected to the ResetPassword page - a page that is yet to be added. Things are working as expected. In the next tutorial we shall add the ResetPassword page. Thanks!


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