Table of Contents (top down ↓)
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!
Similar Posts
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.