ASP.NET Core 3.1 - Password Recovery
This article will describe the implementation of an automated password reset process for a user. I will assume you have downloaded the ASP.NET Core 3.1 - Users Without Identity Project or created a new ASP.NET Core 3.1 Razor Pages project. See Tutorial: Get started with Razor Pages in ASP.NET Core. You should review the earlier articles of the Users Without Identity Project series.
Users Without Identity Project and Article Series
Creation Series
- ASP.NET Core 3.1 - Users Without Identity
- ASP.NET Core 3.1 - User Entity
- ASP.NET Core 3.1 - Password Hasher
- ASP.NET Core 3.1 - User Management
- ASP.NET Core 3.1 - Admin Role
- ASP.NET Core 3.1 - Cookie Validator
- ASP.NET Core 3.1 - Concurrency Conflicts
- ASP.NET Core 3.1 - Must Change Password
- ASP.NET Core 3.1 - User Database Service
- ASP.NET Core 3.1 - Rename Related Entities
2FA Series
- ASP.NET Core 3.1 - 2FA Without Identity
- ASP.NET Core 3.1 - 2FA User Tokens
- ASP.NET Core 3.1 - 2FA Cookie Schemes
- ASP.NET Core 3.1 - 2FA Authenticating
- ASP.NET Core 3.1 - 2FA Sign In Service
- ASP.NET Core 3.1 - 2FA QR Code Generator
- ASP.NET Core 3.1 - Admin 2FA Requirement
- ASP.NET Core 3.1 - 2FA Admin Override
Enhanced User Series
- ASP.NET Core 3.1 - Enhanced User Without Identity
- ASP.NET Core 3.1 - 2FA Recovery Codes
- ASP.NET Core 3.1 - Login Lockout
- ASP.NET Core 3.1 - Created And Last Login Date
- ASP.NET Core 3.1 - Security Stamp
- ASP.NET Core 3.1 - Token Service
- ASP.NET Core 3.1 - Confirmed Email Address
- ASP.NET Core 3.1 - Password Recovery
UWIP v2 implements a forgot password process. The user can request an email sent to the confirmed email address.
The user must provide their confirmed email address on the forgot password page.
If the email address is found and confirmed, an email is sent with a link to the reset password page. The link includes a PasswordReset token based on the AppUser's SecurityStamp and provided by the TokenService.
Pages > Account > ForgotPassword > OnPostAsync:
var code = await _tokenService.GeneratePasswordResetTokenAsync(appUser).ConfigureAwait(false); var callbackUrl = Url.Page( "/Account/ResetPassword", pageHandler: null, values: new { code }, protocol: Request.Scheme); var subject = "Reset password."; var htmlMessage = "Please reset your password by " + $"<a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>."; var textMessage = "Please reset your password by coping and pasting this link: \r\n \r\n" + $"{callbackUrl} \r\n \r\nto the address bar of your browser."; await _emailSender.SendEmailAsync(Email, subject, textMessage, htmlMessage); return RedirectToPage("./ForgotPasswordConfirmation");
The reset password page uses the email address to query the user. If the token is valid, the new password is hashed and the AppUser's PasswordHash is updated.
Pages > Account > ResetPassword > OnPostAsync:
var appUser = await _userService.GetAppUserByEmailAsync(Input.Email).ConfigureAwait(false); // Don't reveal that the user does not exist if (appUser == null) return RedirectToPage("./ResetPasswordConfirmation"); if (await _tokenService.ValidatePasswordResetTokenAsync(appUser, Input.Code).ConfigureAwait(false)) { if (!await _userService.UpdateAppUserPasswordAsync(appUser.Id, Input.Password).ConfigureAwait(false)) throw new InvalidOperationException($"An error occurred resetting an AppUser password."); } else { ModelState.AddModelError(string.Empty, "Invalid or expired code."); ShowInvalid = true; return Page(); } return RedirectToPage("./ResetPasswordConfirmation");
If the token is invalid, the ResetPassword page uses the ShowInvalid property to display a "Try again?" link which redirects to the forgot password page.
Update 02/23/2021
I updated the article links.
Comments(0)