I’m an avid user on StackOverflow in questions about Asp.Net Identity and I attempt to answer most of the interesting questions. And the same question comes up quite often: users try to confirm their email via a confiramtion link or reset their password via reset link and in both cases get “Invalid Token” error.

There are a few possible solutions to this problem.

Password Reset Token vs Email Confirmation Token

First I would like to go through token generation process, as it might be confusing.
If you explore UserManager object, you will find 3 public methods that can generate you a token:


Difference in them is small but important.
If you look on the signature of GenerateUserTokenAsync

public virtual async Task<string> GenerateUserTokenAsync(string purpose, TKey userId)

You see it is taking a purpose string and a User.Id.

If you look on the source code of GeneratePasswordResetTokenAsync and GenerateEmailConfirmationTokenAsync, you will see this:

    public virtual Task<string> GeneratePasswordResetTokenAsync(TKey userId)
        return GenerateUserTokenAsync("ResetPassword", userId);

    public virtual Task<string> GenerateEmailConfirmationTokenAsync(TKey userId)
        return GenerateUserTokenAsync("Confirmation", userId);

So PasswordReset and EmailConfirmation tokens are just tokens generated with a specific purpose. And if you follow the code execution path, you’ll see that the purpose string is used as a part of encryption, as one of the encryption keys for the tokens.

When the tokens comes back into the system and you call ResetPasswordAsync, the token goes through the reverse process – it eventually ends in VerifyUserTokenAsync(userId, "ResetPassword", token) where “ResetPassword” is a purpose. If you call ConfirmEmailAsync then the token is passed to VerifyUserTokenAsync(userId, "Confirmation", token) where “Confirmation” is a purpose.

Do you see the pattern here? Value of purpose string is one of the encryption keys in the token, so it is important to provide the correct key, i.e. you can’t call ResetPasswordAsync on a token generated by GenerateEmailConfirmationTokenAsync

Token is changed in transit

Current (1st May 2015) Visual Studio 2013 MVC template with Identity includes these lines for account email confirmation:

string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
await UserManager.SendEmailAsync(user.Id, "Confirm your account", "Please confirm your account by clicking <a href=\"" + callbackUrl + "\">here</a>");

I.e. token is generated, then used as a parameter in generating a URL and then passed into an email text. Now, token is a Base64 string. Base64 strings are not safe to pass as a URL parameters as special characters there can be interpreted incorrectly by browsers. So you need to do url-encode them to avoid mis-interpretation of the token:

string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
code = System.Web.HttpUtility.UrlEncode(code);
// the rest of the snippet

Don’t forget that there are 2 places where you generate tokens – on user registration and on password reset. You need to add Url-Encoding in both of the places.

Security Stamp is Null

Credit goes to this answer on SO. If your UserManager supports Security Stamp and if you use the framework-provided classes, it supports this. Your token generation process uses this piece of code:

string stamp = null;
if (manager.SupportsUserSecurityStamp)
    stamp = await manager.GetSecurityStampAsync(user.Id);
writer.Write(stamp ?? "");  

where writer is a MemoryStream writer that eventually is passed through data-protection, Base64 and returned to your controller. In other words, if SecurityStamp on your user record is null, String.Empty is written to the token.

On token validation process you can see

if (manager.SupportsUserSecurityStamp)
    var expectedStamp = await manager.GetSecurityStampAsync(user.Id).WithCurrentCulture();
    return stamp == expectedStamp;

So if expectedStamp is null again, we are comparing String.Empty to null. And they are not equal.

This looks like a bug in Identity, but possibly this is intentional for security reasons. I have started a discussion about this on Identity forum, maybe this will be fixed.

So solution to the problem is to make sure that SecurityStamp always has a value on your user records.

Machine Keys are not matching

Another reason for this problem might come from your different servers. If your token was generated on one server, and then attempting to validate it on another. Reason for that is that the token is protected via MachineKey.Protect. That is configured on OWIN initialisation:

builder.Properties[Constants.SecurityDataProtectionProvider] = new MachineKeyDataProtectionProvider().ToOwinFunction();

and comes down to using of MachineKey.Protect in a wrapper MachineKeyDataProtector

namespace Microsoft.Owin.Host.SystemWeb.DataProtection
    internal class MachineKeyDataProtector
        private readonly string[] _purposes;

        public MachineKeyDataProtector(params string[] purposes)
            _purposes = purposes;

        public virtual byte[] Protect(byte[] userData)
            return MachineKey.Protect(userData, _purposes);

        public virtual byte[] Unprotect(byte[] protectedData)
            return MachineKey.Unprotect(protectedData, _purposes);

And if your servers have different machine keys, token from one server will not validate on another server.

The solution to this problem is to provide a <MachineKey> section in your web.config and make sure that is always the same on all the servers.

There are a multiple guides online how to generated a MachineKey, I’ll leave that for you to discover.

  • Brian Behm

    This is a great article. I am following the suggestions listed here but had a question. Is there a difference to the wasy ASP.NET Web API 2 generates the password reset token from the way it would be done in ASP.NET MVC 5?

    My application is using MVC 5 but for some internal controls, I have had to setup a separate ASP.NET Web API service. They both share the same database of users. When I generate the password reset token from the Web API it doesn’t validate in the MVC 5 application. They are on the same servers so the machine key should not be an issue.

    Both are making a call to UserManager.GeneratePasswordResetTokenAsync(user.id) though they are separate pieces of code since they are hosted separately. They appear to have been generated identically from the default project templates using “Individual Accounts” for authentication.

    Thanks for the article and any insight you could provide regarding my question.


    • Uh.. that’s a good question. In theory there should be no difference. On practice there you have it. But can you validate tokens in MVC generated in MVC? and do tokens generated in WebApi validate on WebApi?

      • Brian Behm

        While testing your question about whether the generated tokens are valid in their own enviroment, I found out that when I am receiving my tokens in either my teset WebApi method or the method in my MVC site, I have found that the token is missing any “+” characters that it orignially contained. The “+” is replaced by a space. When I manipulate the string to put the “+” back instead of the ” “, my tokens validate.

        I have verified in my password reset email that he “+” characters to exist their so maybe they are being replaced somewhere in the MVC/WebApi model binding?

        • Brian, that is a URL encoding issue. See this question: http://stackoverflow.com/questions/1005676/urls-and-plus-signs

          So you need to URL encode the token before you pass it as a GET parameter – see “Token is changed in transit” part of this post.

          • Fletcher Bumpus

            I know this is an OLD post/response, but I’d like to add my 2 cents. Using Identify with ASP.Net Core I was having the Invalid Token issue and couldn’t figure it out until I realized that I was sending the email via text instead of HTML. I was still able to click the link, but the result wasn’t valid. Once I switched to sending as HTML, my tokens came across just fine.

          • Thank you for your $.02 – this can be a solution for somebody -)

        • Brian Behm

          While creating the reset URL which is emailed to the user, I am now encoding the token which seems to make it work better. Here is a snippet of my code with the HttpUtility.UrlEncode(code):

          var code = await UserManager.GeneratePasswordResetTokenAsync(user.Id);

          var callbackUrl = string.Format(“{0}?userid={1}&code={2}”, Config.WelcomeCallbackUrl, user.Id, HttpUtility.UrlEncode(code));

          _emailService.SendPasswordResetEmail(user.Email, callbackUrl);

          I did get this to work in my “localhost” environment but it still isn’t working in production. But, even in production they are still on the same machine so I don’t suspect that the machine key is an issue.

          • I’m guessing here now, as I don’t remember details of machine key implementations. Try adding machine key to your web.config – see if this changes anything.

          • Brian Behm

            I will give that a shot but another question I am looking into is what goes into generating the token. For example, I technically have two sites in IIS. One is for my web app and one is for my api. So, does the domain of the site go into the algorithm for the token? If I generate a token on http://api.mysite.com and try to verify it on http://mysite.com, does it report as invalid because the domains do not match. In my case the same user database is used so each site would have the same security stamp.

          • As far as I remember, domain name does not go into the token. I’ll verify for you though

          • See here: http://aspnetidentity.codeplex.com/SourceControl/latest#src/Microsoft.AspNet.Identity.Owin/DataProtectorTokenProvider.cs search for “public async Task GenerateAsync” token contains current UTC time, user ID, user security stamp (if supported) and purpose (which is a user-provided string). Nothing about domain. But if you want, you can provide your domain name as a purpose and then verify it with the same domain name afterwards.

          • Ah, I’m wrong again. I completely forgot that Data Protection Provider takes “appName”. Currently I’m looking on how to specify app name for both WebApi and MVC

          • Brian, I’ve gone through OWIN source code and indeed application name is used in protection of token. Only it is done indirectly – when OWIN is creationg DataProtector, it takes AppName and by default it is IIS App Name that contains domain name. So yes, domain name is part of the token. And unfortunately I have not found a way to set AppName in OWIN. I know it is stored in `app.Properties[“host.AppName”]` but I could not override it -(

          • Brian Behm

            Adding a machine key to each application’s web site did solve my problem. I had to remove the “IsolateApp” suffix from the machine key values. This can also be accomplished by unchecking the “Generate a unique key for each application” options in the IIS GUI for each site.

            Thanks for all your help and for this article.

          • Glad this worked for you and you found the solution!

  • Martin Bajanik

    great article! However, one question regarding the section “Token is changed in transit”:

    Aren’t you double encoding the token? Url.Action() URL encodes the action parameters on it’s own (atleast in the latest version), so the subsequent encoding actually causes mis-interpretation.

    • kiran.b.chandran

      @martinbajanik:disqus Yes I think the Url.Action is doing that encoding in latest version. So encoding is only needed if you build the url from string.

  • Okezie Okpara

    Thanks for this, but how come it is not mentioned in any documentation

    • While writing documentation there is an infinite amount of scenarios that can be covered. And also the longer the documentation, the less chances of it to be read. So it is a compromise of elaboration and amount of time spent on documentation.

  • AJ

    Wow! really? I posted a minor question here and its removed for no reason. Do you keep only positive comments?

    • Em.. No, I’ve never removed any comments from here. In fact, I’ve been travelling last 3 days, so been offline and not seen any comments apart from this one. If anything you posted did not come up here – this is not doing of my hands. Most likely Disqus system had a glitch of some sort and your comment got lost – that happened to at least one of my comments before.

      • AJ

        No worries Trailmax, appreciate your response. I will post it again.

  • There you go – it was held in spam queue in Disqus system.

    • AJ

      Ahh I see, probably because I had a link in the description. Thanks mate!

  • Dave Ronson

    A great article which I got to while searching for the logic behind token creation. In case it helps others coming here in the future, it appears you no longer need to explicitly URL encode the token/code in ASP.NET Core Identity as it is properly encoded in URL.Action:

    var callbackUrl = Url.Action(“ConfirmEmail”, “Account”, new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);

  • TheBigRic

    Great article again.
    I found that IIS by default sets the machine key to AutoGenerate. On my production machines I see however that every application pool recycle this key changes. Therefore people who will confirm their token after a pool recycle are unable to validate because the token can’t be unprotected.
    However, in this post and probably more important in this case, the official documentation of working with machine keys states:
    “The default value is correct for a single server deployment. You do not need to change the default settings unless your application is deployed in a Web farm”
    So I’m confused on my findings. Any thoughts on this?

    • My experience is the same – if there is no machine key and app is recycled, then keys generated before the recycle are no longer valid after recycle.
      And yes, I’ve seen that guide as well, so something is not quite right with it.

      • TheBigRic

        This should be documented somewhere! Wow, I’m surprised I had to search so hard to find this, one might think this is a bigger issue.
        Thanx for your swift reply!