Asp.NET Identity 2 giving "Invalid Token" error

79,110

Solution 1

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);

Solution 2

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.

Solution 3

I was getting the "Invalid Token" error even with code like this:

var emailCode = UserManager.GenerateEmailConfirmationToken(id);
var result = UserManager.ConfirmEmail(id, emailCode);

In my case the problem turned out to be that I was creating the user manually and adding him to the database without using the UserManager.Create(...) method. The user existed in the database but without a security stamp.

It's interesting that the GenerateEmailConfirmationToken returned a token without complaining about the lack of security stamp, but that token could never be validated.

Solution 4

Other than that, I've seen the code itself fail if it's not encoded.

I've recently started encoding mine in the following fashion:

string code = manager.GeneratePasswordResetToken(user.Id);
code = HttpUtility.UrlEncode(code);

And then when I'm ready to read it back:

string code = IdentityHelper.GetCodeFromRequest(Request);
code = HttpUtility.UrlDecode(code);

To be quite honest, I'm surprised that it isn't being properly encoded in the first place.

Solution 5

In my case, our AngularJS app converted all plus signs (+) to empty spaces (" ") so the token was indeed invalid when it was passed back.

To resolve the issue, in our ResetPassword method in the AccountController, I simply added a replace prior to updating the password:

code = code.Replace(" ", "+");
IdentityResult result = await AppUserManager.ResetPasswordAsync(user.Id, code, newPassword);

I hope this helps anyone else working with Identity in a Web API and AngularJS.

Share:
79,110

Related videos on Youtube

Julio Schurt
Author by

Julio Schurt

Updated on January 02, 2022

Comments

  • Julio Schurt
    Julio Schurt over 2 years

    I'm using Asp.Net-Identity-2 and I'm trying to verify email verification code using the below method. But I am getting an "Invalid Token" error message.

    • My Application's User Manager is like this:

      public class AppUserManager : UserManager<AppUser>
      {
          public AppUserManager(IUserStore<AppUser> store) : base(store) { }
      
          public static AppUserManager Create(IdentityFactoryOptions<AppUserManager> options, IOwinContext context)
          {
              AppIdentityDbContext db = context.Get<AppIdentityDbContext>();
              AppUserManager manager = new AppUserManager(new UserStore<AppUser>(db));
      
              manager.PasswordValidator = new PasswordValidator { 
                  RequiredLength = 6,
                  RequireNonLetterOrDigit = false,
                  RequireDigit = false,
                  RequireLowercase = true,
                  RequireUppercase = true
              };
      
              manager.UserValidator = new UserValidator<AppUser>(manager)
              {
                  AllowOnlyAlphanumericUserNames = true,
                  RequireUniqueEmail = true
              };
      
              var dataProtectionProvider = options.DataProtectionProvider;
      
              //token life span is 3 hours
              if (dataProtectionProvider != null)
              {
                  manager.UserTokenProvider =
                     new DataProtectorTokenProvider<AppUser>
                        (dataProtectionProvider.Create("ConfirmationToken"))
                     {
                         TokenLifespan = TimeSpan.FromHours(3)
                     };
              }
      
              manager.EmailService = new EmailService();
      
              return manager;
          } //Create
        } //class
      } //namespace
      
    • My Action to generate the token is (and even if I check the token here, I get "Invalid token" message):

      [AllowAnonymous]
      [HttpPost]
      [ValidateAntiForgeryToken]
      public ActionResult ForgotPassword(string email)
      {
          if (ModelState.IsValid)
          {
              AppUser user = UserManager.FindByEmail(email);
              if (user == null || !(UserManager.IsEmailConfirmed(user.Id)))
              {
                  // Returning without warning anything wrong...
                  return View("../Home/Index");
      
              } //if
      
              string code = UserManager.GeneratePasswordResetToken(user.Id);
              string callbackUrl = Url.Action("ResetPassword", "Admin", new { Id = user.Id, code = HttpUtility.UrlEncode(code) }, protocol: Request.Url.Scheme);
      
              UserManager.SendEmail(user.Id, "Reset password Link", "Use the following  link to reset your password: <a href=\"" + callbackUrl + "\">link</a>");
      
              //This 2 lines I use tho debugger propose. The result is: "Invalid token" (???)
              IdentityResult result;
              result = UserManager.ConfirmEmail(user.Id, code);
          }
      
          // If we got this far, something failed, redisplay form
          return View();
      
      } //ForgotPassword
      
    • My Action to check the token is (here, I always get "Invalid Token" when I check the result):

      [AllowAnonymous]
      public async Task<ActionResult> ResetPassword(string id, string code)
      {
      
          if (id == null || code == null)
          {
              return View("Error", new string[] { "Invalid params to reset password." });
          }
      
          IdentityResult result;
      
          try
          {
              result = await UserManager.ConfirmEmailAsync(id, code);
          }
          catch (InvalidOperationException ioe)
          {
              // ConfirmEmailAsync throws when the id is not found.
              return View("Error", new string[] { "Error to reset password:<br/><br/><li>" + ioe.Message + "</li>" });
          }
      
          if (result.Succeeded)
          {
              AppUser objUser = await UserManager.FindByIdAsync(id);
              ResetPasswordModel model = new ResetPasswordModel();
      
              model.Id = objUser.Id;
              model.Name = objUser.UserName;
              model.Email = objUser.Email;
      
              return View(model);
          }
      
          // If we got this far, something failed.
          string strErrorMsg = "";
          foreach(string strError in result.Errors)
          {
              strErrorMsg += "<li>" + strError + "</li>";
          } //foreach
      
          return View("Error", new string[] { strErrorMsg });
      
      } //ForgotPasswordConfirmation
      

    I don't know what could be missing or what's wrong...

  • Eric Carlson
    Eric Carlson almost 9 years
    It only needs to be encoded when it is used as a query string value for a reset link. It's possible to use it without encoding if you are providing a password reset form inside of an application where the code gets passed as a hidden value or something similar.
  • Aaron Hudon
    Aaron Hudon over 8 years
    Your comment to not decode the code does not work for me. Only decoding the code will result in success.
  • JoeCool-Avismón
    JoeCool-Avismón over 8 years
    Concerning the encoding/decoding to avoid spaces and other simbols interference I'm using this proposal that works like a charm: stackoverflow.com/questions/27535233/…
  • Victor
    Victor over 8 years
    For a more formal approach, I would recommend var callbackUrl = new Uri(Request.RequestUri, RequestContext.VirtualPathRoot).AbsoluteUri + $"#/resetPassword?username={WebUtility.UrlEncode(user.UserNa‌​me)}&code={WebUtilit‌​y.UrlEncode(code)}"; to correctly url encode username and code to a client page (for example Angular) to let the user set password and finalize request
  • João Pereira
    João Pereira almost 8 years
    Just one note to this awesome answer! :) The token MUST be UrlEncoded, but it shouldn't be UrlDecoded, at least in MVC when received as a method parameter, since it is automatically decoded. If we decode it again, we invalidate the token since the + character gets replaced with a white space.
  • user1069816
    user1069816 almost 8 years
    In my case the users had been migrated from an old database so had null Security Stamps, I ran this to fix it: UPDATE AspNetUsers SET SecurityStamp = NewID()
  • Bart Verkoeijen
    Bart Verkoeijen about 7 years
    The default token is base64 encoded, which is not URL safe and requires URL encoding. You can override or wrap the token provider, and return base64url encoded tokens instead, avoiding the special characters like you did already.
  • Alternatex
    Alternatex almost 7 years
    @AaronHudon Probably dependent on whether you're sending it through the url string or in the request body (post).
  • TNT
    TNT over 6 years
    I suggest using UPDATE AspNetUsers SET SecurityStamp = NewID() WHERE SecurityStamp is null . In my case, SecurityStamp of some users are fine, I prefer to don't mess with them.
  • Choco
    Choco over 5 years
    It seems to depend on if your using WebAPI or MVC controllers. The model Binder on the MVC controller URL Decodes it by default!
  • cyptus
    cyptus almost 5 years
    this solved not my problem bc i need to use the tokens across projects, instances and computers. i implemented a custom AES encryption for this, see my answer for details: stackoverflow.com/a/56355282/1216595
  • Christopher Berman
    Christopher Berman over 4 years
    One thing to keep in mind is that Identity, left to its own devices, generates guids in lowercase, whereas NewID() returns an uppercase guid (at least in SSMS). Consider using LOWER(NewID())
  • Yeronimo
    Yeronimo over 4 years
    For me it was actually on checking the token. I pulled user by my repo instead of UserManager, so called with my repo user the ResetPasswordAsync. Same issue basically
  • user2904995
    user2904995 over 4 years
    How to make ConfirmEmailAsync return failed instead of success, if that token is already being used once. Like user tries to revisits the link from his/her email address?
  • trailmax
    trailmax over 4 years
    @user2904995 to make token invalid, you need to change SecurityStamp field. This will invalidate all the previously valid tokens, including those that have been used in the past.
  • Krusty
    Krusty about 4 years
    Solved the issue in an Asp Net Core 3.1 application using the solution #3
  • sairfan
    sairfan about 4 years
    wonderful var encodedToken = HttpUtility.UrlEncode(token); did the magic
  • Krusty
    Krusty almost 4 years
    I hit this answer again. The last time I solved it using a single instance of the UserManager registering the service that consumes the UserManager as singleton. In this other projcet instead if I do the same it throws an exception saying I can't register that service as singleton because UserManager requires a Transient scope. Your solution above doesn't compile (lot of issues I can report). So what might be a valid fix for this? The problem is clearly the #3 (different instances of token providers)
  • Krusty
    Krusty almost 4 years
    I fixed it again. The issue was caused by the lack of SecurityStamp column in the User table. I had removed it but without that column it doesn't work
  • Victor HD
    Victor HD about 3 years
    Hey Sr. Thanks by your light! I did it in the following way in ASP.NET Core 5.0: Encoding.UTF8.GetString(Convert.FromBase64String(code));
  • Ahmed Suror
    Ahmed Suror over 2 years
    EXACTLY!!! You saved my day, thank you.
  • walter33
    walter33 over 2 years
    Thank you very much for this extensive description and code fragments. Thanks a lot.
  • cheny
    cheny over 2 years
    @walter33 Really happy that it helped :)
  • Elyas Dolatabadi
    Elyas Dolatabadi about 2 years
    Thank you, I filled SecurityStamp column in my users table and it fixed my problem.