Manually validating a password reset token in ASP.NET Identity

Jeremy Cook picture Jeremy Cook · May 22, 2014 · Viewed 13.9k times · Source

I would like to manually validate a password reset token in ASP.NET Identity 2.0. I'm trying to create my own version of UserManager.ResetPasswordAsync(string userId, string token, string newPassword) that takes and IdentityUser instead of userId like this:

UserManager.ResetPasswordAsync(IdentityUser user, string token, string newPassword)

Not sure if I am doing this right, but here I am attempting to validate the code that was emailed to the user in an earlier step. I have not modified the code/token that sends the email to the user and generates the code. I am assuming this is the correct method to call, but the purpose argument is incorrect. (I tried passing "ASP.NET Identity" but no dice.)

if (await userManager.UserTokenProvider.ValidateAsync(purpose: "?", token: code, manager: userManager, user: user))
{
    return IdentityResult.Success;
}
else
{
    return new IdentityResult("Invalid code.");
}

If someone could fill me in on the details of how it works out of the box, or point me at Microsoft's source code for UserManager.ResetPasswordAsync(IdentityUser user, string token, string newPassword) that would be most appreciated!

Answer

Jeremy Cook picture Jeremy Cook · May 22, 2014

I overcame my problem by setting the purpose to "ResetPassword".

Below is a snippet of the final result in case someone wants to do something similar. It is a method in my ApplicationUserManager class. Realize, though, that some of the exception handling that Microsoft implements is missing or not localized because certain private variables, methods, and resources used in their code are inaccessible. It's unfortunate they did not make that stuff protected so that I could have gotten at it. The missing ThrowIfDisposed method call in particular is interesting (and bazaar) to me. Apparently they are anticipating method calls after an instance has been disposed in order to provide a friendlier error message and avoid the unexpected.

public async Task<IdentityResult> ResetPasswordAsync(IdentityUser user,
    string token, string newPassword)
{
    if (user == null)
    {
        throw new ArgumentNullException("user");
    }

    // Make sure the token is valid and the stamp matches.
    if (!await UserTokenProvider.ValidateAsync("ResetPassword", token, 
        this, user))
    {
        return IdentityResult.Failed("Invalid token.");
    }

    // Make sure the new password is valid.
    var result = await PasswordValidator.ValidateAsync(newPassword)
        .ConfigureAwait(false);
    if (!result.Succeeded)
    {
        return result;
    }

    // Update the password hash and invalidate the current security stamp.
    user.PasswordHash = PasswordHasher.HashPassword(newPassword);
    user.SecurityStamp = Guid.NewGuid().ToString();

    // Save the user and return the outcome.
    return await UpdateAsync(user).ConfigureAwait(false);
}