Adding password reset using ASP.NET Core Identity

Password reset features are an important part of any website that allows visitors to register accounts and sign in. Such features are commonly used as a recovery mechanism by users who forget their password.

You can implement email-based password reset functionality using the Kentico.Membership API (provided as part of the Xperience.AspNetCore.WebApp integration package) in combination with ASP.NET Core Identity.

To learn how to set up password reset functionality for the Xperience administration interface, see: Configuring password resets for Xperience administration

Prerequisites

Before you start working on password reset functionality, you need to perform the following:

Xperience password features

The built-in password features of Xperience affect the behavior of password resets on ASP.NET Core sites:

  • The time period during which password reset requests links are valid can be specified in hours via the Reset password interval setting.
  • The Password policy settings apply when resetting passwords using the Xperience Core Identity implementation.
  • Password format ensures that any passwords submitted by users on your Core website are stored in the format configured for the Xperience application.

    If your administration application uses a custom salt value when generating password hashes, you also need to set the same value for the Core application. Check the appSettings section of your administration application's web.config for the CMSPasswordSalt key. If the key is present, copy it to the appSettings file used by your Core project.

Manual resetting of passwords through the Xperience administration is also possible. Administrators can change passwords by editing users in the Users application on the Password tab.

  • If you reset the password using the Generate new password button, the user receives a notification email, based on the Membership - Changed password email template, containing the new password.
  • If you change the password manually using the Change password button, the user receives a notification email, based on the Membership - Password reset confirmation email template, informing them of the password change. This email does not contain the new password.

Setting up password reset functionality

To allow users to reset their passwords on your site, you need to implement the required logic using the Identity integration API provided by Xperience.

Configuring the password reset email settings

The password reset implementation sends emails using the Xperience API. Configure the email settings through the administration interface of the connected Xperience application:

  • Set up SMTP servers. See Configuring SMTP servers for more information.
  • You can use the Email queue application to monitor the emails (if the email queue is enabled).
  • Set the sender address for the emails in Settings -> System -> No-reply email address.

This implementation uses the MVC approach:

  1. Create a new controller class in your project or edit an existing one.
  2. Provide instances of the ApplicationUserManager and IMessageService classes from Kentico.Membership using dependency injection.

  3. Implement two actions to allow users to submit password reset requests:
    • A basic GET action that displays an email address entry form.
    • A POST action that handles sending of password reset emails to the specified address.
  4. Perform the following steps within the POST action:
    1. Get the Kentico.Membership.ApplicationUser object for the specified email address – call the ApplicationUserManager.FindByEmailAsync method.
    2. Generate a token for the password reset link by calling the ApplicationUserManager.GeneratePasswordResetTokenAsync method. 
    3. Prepare the URL of the reset link, with the user ID and generated token as query string parameters.
    4. Send the password reset email by calling the IMessageService.SendEmailAsync method. You need to specify parameters with the email subject and content (including the reset link).

  5. Add another action that handles the password reset requests – validate the token and display a password reset form. Call the ApplicationUserManager.VerifyUserTokenAsync method to verify the validity of the password reset token.

  6. Add a POST action that accepts the input of the password reset form. Call the ApplicationUserManager.ResetPassword method to set new passwords for users.

    Password reset controller example
    using System;
    using System.Threading.Tasks;
    
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Identity;
    
    using Kentico.Membership;
    
    using LearningKitCore.Models.Users.PasswordReset;
    
    namespace LearningKitCore.Controllers
    {
        public class PasswordResetController : Controller
        {
            private readonly ApplicationUserManager<ApplicationUser> userManager;
            private readonly IMessageService messageService;
    
    
            public PasswordResetController(ApplicationUserManager<ApplicationUser> userManager,
                                     IMessageService messageService)
            {
                this.userManager = userManager;
                this.messageService = messageService;
            }
    
            /// <summary>
            /// Allows visitors to submit their email address and request a password reset.
            /// </summary>
            public IActionResult PasswordResetRequest()
            {
                return View();
            }
    
            /// <summary>
            /// Generates a password reset request for the specified email address.
            /// Accepts the email address posted via the RequestPasswordResetViewModel.
            /// </summary>
            [HttpPost]
            [ValidateAntiForgeryToken]
            public async Task<IActionResult> RequestPasswordReset(PasswordResetRequestViewModel model)
            {
                // Validates the received email address based on the view model
                if (!ModelState.IsValid)
                {
                    return View(model);
                }
    
                // Gets the user entity for the specified email address
                ApplicationUser user = await userManager.FindByEmailAsync(model.Email);
    
                if (user != null)
                {
                    // Generates a password reset token for the user
                    string token = await userManager.GeneratePasswordResetTokenAsync(user);
    
                    // Prepares the URL of the password reset link (targets the "ResetPassword" action)
                    // Fill in the name of your controller
                    string resetUrl = Url.Action(nameof(PasswordResetController.PasswordReset), nameof(PasswordResetController), new { userId = user.Id, token }, Request.Scheme);
    
                    // Creates and sends the password reset email to the user's address
                    await messageService.SendEmailAsync(user.Email, "Password reset request",
                        $"To reset your account's password, click <a href=\"{resetUrl}\">here</a>");
                }
    
                // Displays a view asking the visitor to check their email and click the password reset link
                return View("CheckYourEmail");
            }
    
            /// <summary>
            /// Handles the links that users click in password reset emails.
            /// If the request parameters are valid, displays a form where users can reset their password.
            /// </summary>
            public async Task<IActionResult> PasswordReset(int? userId, string token)
            {
                if (String.IsNullOrEmpty(token))
                {
                    return NotFound();
                }
    
                ApplicationUser user = await userManager.FindByIdAsync(userId.ToString());
    
                try
                {
                    // Verifies the parameters of the password reset request
                    // True if the token is valid for the specified user, false if the token is invalid or has expired
                    // By default, the generated tokens are single-use and expire in 1 day
                    if (await userManager.VerifyUserTokenAsync(user, userManager.Options.Tokens.PasswordResetTokenProvider, "ResetPassword", token))
                    {
                        // If the password request is valid, displays the password reset form
                        var model = new PasswordReseViewModel
                        {
                            UserId = userId.Value,
                            Token = token
                        };
    
                        return View(model);
                    }
    
                    // If the password request is invalid, returns a view informing the user
                    return View("PasswordResetResult", ViewBag.Success = false);
                }
                catch (InvalidOperationException)
                {
                    // An InvalidOperationException occurs if a user with the given ID is not found
                    // Returns a view informing the user that the password reset request is not valid
                    return View("PasswordResetResult", ViewBag.Success = false);
                }
            }
    
            /// <summary>
            /// Resets the user's password based on the posted data.
            /// Accepts the user ID, password reset token and the new password via the ResetPasswordViewModel.
            /// </summary>
            [HttpPost]
            [ValidateAntiForgeryToken]
            public async Task<IActionResult> ResetPassword(PasswordReseViewModel model)
            {
                // Validates the received password data based on the view model
                if (!ModelState.IsValid)
                {
                    return View(model);
                }
    
                bool result = false;
    
                ApplicationUser user = await userManager.FindByIdAsync(model.UserId.ToString());
    
                // Changes the user's password if the provided reset token is valid
                if (user != null && (await userManager.ResetPasswordAsync(user, model.Token, model.Password)).Succeeded)
                {
                    // If the password change was successful, displays a message informing the user
                    result = true;
                }
    
                // Occurs if the reset token is invalid
                // Returns a view informing the user that the password reset failed
                return View("PasswordResetResult", ViewBag.Success = result);            
            }
        }
    }

  7. We recommend creating view models for your password reset actions and input forms:

    • For the reset request form, the view model needs to validate and transfer the email address value.

    • For the password reset form, the view model must contain the user ID, reset token and the new password.

      Password reset view model example
      using System.ComponentModel;
      using System.ComponentModel.DataAnnotations;
      
      
      namespace LearningKitCore.Models.Users.PasswordReset
      { 
          public class PasswordReseViewModel
          {
              public int UserId
              {
                  get;
                  set;
              }
      
              public string Token
              {
                  get;
                  set;
              }
      
              [DataType(DataType.Password)]
              [Required(ErrorMessage = "The Password cannot be empty.")]
              [DisplayName("Password")]
              [MaxLength(100, ErrorMessage = "The Password cannot be longer than 100 characters.")]
              public string Password
              {
                  get;
                  set;
              }
      
              [DataType(DataType.Password)]
              [DisplayName("Password confirmation")]
              [MaxLength(100, ErrorMessage = "The Password cannot be longer than 100 characters.")]
              [Compare("Password", ErrorMessage = "The entered passwords do not match.")]
              public string PasswordConfirmation
              {
                  get;
                  set;
              }
          }
      }

  8. Design the user interface for the password reset functionality on your website:
    • Add a button or link targeting the RequestPasswordReset action into an appropriate location on your website (for example as a "Forgotten password" link under the sign-in form).
    • Create a view for the RequestPasswordReset action that displays an email submission form.
    • Create a view that informs users to check their email and click a link to reset their password (CheckYourEmail view in the example).
    • Create a view for the ResetPassword action that displays a password reset form. We recommend using a strongly typed view based on your password reset view model.
    • Create a view for the results of the ResetPassword action (PasswordResetResult in the example).

Users can now reset their passwords on your site. When a user clicks the password reset button or link and submits their email address, the system sends them an email. The email contains a link (single-use with a 1-day expiration by default) that sends the user to a password reset form, where they can set a new password. The password reset form only works for users who access the URL with a valid token parameter.


Was this page helpful?