Asp.NET Identity 2 giving "Invalid Token" error
Because you are generating token for password reset here:
string code = UserManager.GeneratePasswordResetToken(user.Id);
But actually trying to validate token for email:
result = await UserManager.ConfirmEmailAsync(id, code);
These are 2 different tokens.
In your question you say that you are trying to verify email, but your code is for password reset. Which one are you doing?
If you need email confirmation, then generate token via
var emailConfirmationCode = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
and confirm it via
var confirmResult = await UserManager.ConfirmEmailAsync(userId, code);
If you need password reset, generate token like this:
var code = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
and confirm it like this:
var resetResult = await userManager.ResetPasswordAsync(user.Id, code, newPassword);
I encountered this problem and resolved it. There are several possible reasons.
1. URL-Encoding issues (if problem occurring "randomly")
If this happens randomly, you might be running into url-encoding problems. For unknown reasons, the token is not designed for url-safe, which means it might contain invalid characters when being passed through a url (for example, if sent via an e-mail).
In this case, HttpUtility.UrlEncode(token)
and HttpUtility.UrlDecode(token)
should be used.
As oão Pereira said in his comments, UrlDecode
is not (or sometimes not?) required. Try both please. Thanks.
2. Non-matching methods (email vs password tokens)
For example:
var code = await userManager.GenerateEmailConfirmationTokenAsync(user.Id);
and
var result = await userManager.ResetPasswordAsync(user.Id, code, newPassword);
The token generated by the email-token-provide cannot be confirmed by the reset-password-token-provider.
But we will see the root cause of why this happens.
3. Different instances of token providers
Even if you are using:
var token = await _userManager.GeneratePasswordResetTokenAsync(user.Id);
along with
var result = await _userManager.ResetPasswordAsync(user.Id, HttpUtility.UrlDecode(token), newPassword);
the error still could happen.
My old code shows why:
public class AccountController : Controller
{
private readonly UserManager _userManager = UserManager.CreateUserManager();
[AllowAnonymous]
[HttpPost]
public async Task<ActionResult> ForgotPassword(FormCollection collection)
{
var token = await _userManager.GeneratePasswordResetTokenAsync(user.Id);
var callbackUrl = Url.Action("ResetPassword", "Account", new { area = "", UserId = user.Id, token = HttpUtility.UrlEncode(token) }, Request.Url.Scheme);
Mail.Send(...);
}
and:
public class UserManager : UserManager<IdentityUser>
{
private static readonly UserStore<IdentityUser> UserStore = new UserStore<IdentityUser>();
private static readonly UserManager Instance = new UserManager();
private UserManager()
: base(UserStore)
{
}
public static UserManager CreateUserManager()
{
var dataProtectionProvider = new DpapiDataProtectionProvider();
Instance.UserTokenProvider = new DataProtectorTokenProvider<IdentityUser>(dataProtectionProvider.Create());
return Instance;
}
Pay attention that in this code, every time when a UserManager
is created (or new
-ed), a new dataProtectionProvider
is generated as well. So when a user receives the email and clicks the link:
public class AccountController : Controller
{
private readonly UserManager _userManager = UserManager.CreateUserManager();
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ResetPassword(string userId, string token, FormCollection collection)
{
var result = await _userManager.ResetPasswordAsync(user.Id, HttpUtility.UrlDecode(token), newPassword);
if (result != IdentityResult.Success)
return Content(result.Errors.Aggregate("", (current, error) => current + error + "\r\n"));
return RedirectToAction("Login");
}
The AccountController
is no longer the old one, and neither are the _userManager
and its token provider. So the new token provider will fail because it has no that token in it's memory.
Thus we need to use a single instance for the token provider. Here is my new code and it works fine:
public class UserManager : UserManager<IdentityUser>
{
private static readonly UserStore<IdentityUser> UserStore = new UserStore<IdentityUser>();
private static readonly UserManager Instance = new UserManager();
private UserManager()
: base(UserStore)
{
}
public static UserManager CreateUserManager()
{
//...
Instance.UserTokenProvider = TokenProvider.Provider;
return Instance;
}
and:
public static class TokenProvider
{
[UsedImplicitly] private static DataProtectorTokenProvider<IdentityUser> _tokenProvider;
public static DataProtectorTokenProvider<IdentityUser> Provider
{
get
{
if (_tokenProvider != null)
return _tokenProvider;
var dataProtectionProvider = new DpapiDataProtectionProvider();
_tokenProvider = new DataProtectorTokenProvider<IdentityUser>(dataProtectionProvider.Create());
return _tokenProvider;
}
}
}
It could not be called an elegant solution, but it hit the root and solved my problem.