Search code examples
asp.net-coreentity-framework-coresingle-page-applicationasp.net-core-identity

Reset password using SMS service instead of Email in Asp.Net Core


I want to make an Api endpoint to send password reset token via SMS.

When we use userManager.GeneratePasswordResetTokenAsync(user) to generate a token, the token is a long string that is usually used in an Email message. This message embeds a link containing the token as parameter and is sent to user. User clicks the link and then can change password.

Now, I have this question:

  • How can I use SMS service instead of Email to send this token?
  • Is that right and recommended way that I use the same token created using UserManagerGeneratePasswordResetTokenAsync(...) in SMS or is better to use another token provider to generate a short 6-digit-like string?

Solution

  • Note that GeneratePasswordResetTokenAsync, GenerateChangeEmailTokenAsync and similar methods are using GenerateUserTokenAsync(user, provider, purpose) with specified provider and purpose under the hood.

    We can use GenerateUserTokenAsync(user, provider, purpose) to generate a token the way we want. So this would be my endpoint:

        [HttpPost("sendResetPasswordToken")]
        [ServiceFilter(typeof(ModelValidationFilter))]
        public async Task<IActionResult> SendChangePasswordToken([FromBody] SendChangePasswordTokenDto dto)
        {
            var user = await _userManager.FindByNameAsync(dto.UserName);
            if (user is null)
                return UnprocessableEntity("something went wrong, contact support to resolve the problem");
    
    
            var token = await _userManager.GenerateUserTokenAsync(user, TokenOptions.DefaultPhoneProvider, "ResetPasswordPurpose");
            _SMSManager.SendResetPasswordToken(token, user.PhoneNumber);
            return NoContent();
        }
    

    We set provider param to TokenOptions.DefaultPhoneProvider that is a default phone token provider and set purpose param to "ResetPasswordPurpose" that is an string indicating the purpose of the token.

    To verify this token we use VerifyUserTokenAsync(user, provider, purpose, token) and provide proper params. So here is my ResetPassword endpoint:

        [HttpPost("resetPassword")]
        [ServiceFilter(typeof(ModelValidationFilter))]
        public async Task<IActionResult> ResetPassword([FromBody] ResetPasswordDto dto)
        {
            var user = await _userManager.FindByNameAsync(dto.SSN);
    
            if(user is null)
                return UnprocessableEntity("invalid user token");//actually user not found
    
            var tokenVerified = await _userManager.VerifyUserTokenAsync(user, TokenOptions.DefaultPhoneProvider, "ResetPasswordPurpose", dto.Token);
            if (!tokenVerified)
                return UnprocessableEntity("invalid user token");
    
            var token = await _userManager.GeneratePasswordResetTokenAsync(user);//new token for reseting password
            var result = await  _userManager.ResetPasswordAsync(user, token, dto.NewPassword);
            if (!result.Succeeded)
                return UnprocessableEntity("weak password");
    
            return NoContent();
        }