Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I check if a password reset token is expired?

I'm using ASP.NET Identity, and I have the basic Forgot Password/Reset Password functionality in place.

When you fill out the form that you forgot your password, it creates a reset token using _userManager.GeneratePasswordResetTokenAsync(user)

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ForgotPassword(ForgotPasswordViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = await _userManager.FindByNameAsync(model.Email);
        if (user == null || !(await _userManager.IsEmailConfirmedAsync(user)))
        {
            return View("ForgotPasswordConfirmation");
        }

        var code = await _userManager.GeneratePasswordResetTokenAsync(user);
        var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
        await _emailSender.SendEmailAsync(model.Email, "Reset Password",
               $"Please reset your password by clicking here: <a href='{callbackUrl}'>link</a>");
        return View("ForgotPasswordConfirmation");
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

I noticed that the only validation the Reset Password page has is to check if the code is null, rather than also checking to see if it's still valid or not expired.

[HttpGet]
[AllowAnonymous]
public IActionResult ResetPassword(string code = null)
{
    if (code == null)
    {
        throw new Exception("A code must be supplied for password reset.");
    }
    var model = new ResetPasswordViewModel { Code = code };
    return View(model);
}

It doesn't actually check to see if the token is valid until you attempt to reset your password and it calls _userManager.ResetPasswordAsync(user, model.Code, model.Password)

I'd like to be able to validate that the code is still valid when they hit the Reset Password page to display a message to the user, and not after they attempt to reset their password.

Is there a way to check if it's valid?

like image 725
Steven Avatar asked Sep 30 '17 19:09

Steven


2 Answers

Following code works to verify if the reset token is valid:

1. Create code and send it to user

var code = await this._userManager.GeneratePasswordResetTokenAsync(user);

2. Verify token

[HttpGet]
public async Task<IActionResult> ResetPassword(string UserId, string token)
{
    ...

    ApplicationUser user = //get user;


    if(!await this._userManager.VerifyUserTokenAsync(user,this._userManager.Options.Tokens.PasswordResetTokenProvider, "ResetPassword", token)){
        ViewBag.Message = this._localizer["tokenNotValid"].Value;
    }

    ...
}

See UserManager code: https://github.com/aspnet/Identity/blob/rel/2.0.0/src/Microsoft.Extensions.Identity.Core/UserManager.cs#L29

like image 98
Wouter Avatar answered Oct 20 '22 02:10

Wouter


If you check the UserManager.ResetPasswordAsync(...) method, tracing throug to the VerifyUserTokenAsync method, which simply does:

// Make sure the token is valid
var result = await _tokenProviders[tokenProvider].ValidateAsync(purpose, token, this, user);

You can just do this yourself as well, knowing that:

  • You can ask the DI Framework (e.g. via your controller constructor) for the token provider for your situation;
  • The purpose is just the hardcoded "ResetPassword" string;
  • The token is the code the user is using;
  • The user you should be able to get depending on how your view, e-mail, url, and whatever is set up (the default examples don't cover this I think, but you can easily put the user.Id in the "forgot password url" before the token itself, and extract it when you need it).

Then you can just call ValidateAsync yourself and adjust the view accordingly.

like image 28
Jeroen Avatar answered Oct 20 '22 02:10

Jeroen