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);
ASP.NET Identity 2.0 Invalid Token Randomly
Live Demo Project
I've created a pared-down demo project for you. It's hosted on GitHub here and is live on Azure here. It works as designed (see edits about Azure Websites) and uses a similar but not identical approach as you used.
It started with this tutorial, and then I removed the cruft that came with this NuGet demo code:
Install-Package -Prerelease Microsoft.AspNet.Identity.Samples
For your purposes, my demo code is more relevant than the NuGet sample is, because it focuses just on token creation and validation. In particular, take a look at these two files:
Startup.Auth.cs.
We're instantiating the IDataProtectionProvider
only once per application start.
public partial class Startup
{
public static IDataProtectionProvider DataProtectionProvider
{
get;
private set;
}
public void ConfigureAuth(IAppBuilder app)
{
DataProtectionProvider =
new DpapiDataProtectionProvider("WebApp2015");
// other code removed
}
}
AccountController.cs.
Then within the AccountController
, we're using the static provider instead of creating a new one.
userManager.UserTokenProvider =
new DataProtectorTokenProvider<User>(
Startup.DataProtectionProvider.Create("UserToken"));
Just doing that might remove the bug you're seeing. Here are some questions for you to consider while troubleshooting further.
Are you using two different UserTokenProvider
purposes?
The DataProtectorTokenProvider.Create(string[] purposes)
method takes a purposes
argument. Here is what MSDN has to say about that:
purposes. Additional entropy used to ensure protected data may only be unprotected for the correct purposes.
When you create the user code
, you're using (at least) two different purposes:
user.Id
"ConfirmUser"
and- the purpose of the
ApplicationUserManager
that you retrieve withGetOwinContext()...
.
Here's your code as a snippet.
userManager.UserTokenProvider =
new DataProtectorTokenProvider<User>(provider.Create(user.Id));
manager.UserTokenProvider =
new DataProtectorTokenProvider<User>(provider.Create("ConfirmUser"));
string code = Context
.GetOwinContext()
.GetUserManager<ApplicationUserManager ()
.GenerateEmailConfirmationToken(user.Id)
When you validate the code
, you might be using the wrong purpose. Where do you assign the UserTokenProvider
for the ApplicationUserManager
that you use to confirm the email? It's purposes argument must be the same!
var manager = Context.GetOwinContext()
.GetUserManager<ApplicationUserManager>();
var result = manager.ConfirmEmail(userId, HttpUtility.UrlDecode(code));
There is a strong chance that the token is invalid because you're sometimes using a different UserTokenProvider
purpose for creation than you're using for validation.
Why would this be sometimes? Do a thorough search of your code to find all the places that assign to UserTokenProvider
. Maybe you override it somewhere unexpected (such as in a property or in the IdentityConfig.cs file) so that it seems random.
Has the TokenLifespan
expired?
You've mentioned that the Invalid Token message occurs randomly. It might be that the token has expired. This tutorial notes that the default lifespan is one day. You can change it like this:
manager.UserTokenProvider =
new DataProtectorTokenProvider<ApplicationUser>
(dataProtectionProvider.Create("WebApp2015"))
{
TokenLifespan = TimeSpan.FromHours(3000)
};
Why three UserManager
instances?
Here are some comments on the code that creates the confirmation token. It seems that you're using a three separate UserManager
instances including a derived ApplicationUserManager
type. What's that about?
- What is the type of
manager
here? - Why create a
userManager
instead of using the existingmanager
? - Why use
manager.UserTokenProvider
notuserManager.UserTokenProvider
? - Why are you getting a third
UserManager
instance from theContext
?
Note that I have removed a lot of code to focus on just your token creation.
// 1.
IdentityResult result = manager.Create(user, "Password134567");
if (result.Succeeded)
{
var provider = new DpapiDataProtectionProvider("WebApp2015");
// 2.
UserManager<User> userManager =
new UserManager<User>(new UserStore<User>());
userManager.UserTokenProvider =
new DataProtectorTokenProvider<User>(provider.Create(user.Id));
// 3.
manager.UserTokenProvider =
new DataProtectorTokenProvider<User>(provider.Create("ConfirmUser"));
// 4.
string raw = Context.GetOwinContext()
.GetUserManager<ApplicationUserManager>()
.GenerateEmailConfirmationToken(user.Id)
// remaining code removed
}
I wonder whether we could simplify the above to use just one UserManager
instance as follows.
// 1.
IdentityResult result = manager.Create(user, "Password134567");
if (result.Succeeded)
{
var provider = new DpapiDataProtectionProvider("WebApp2015");
manager.UserTokenProvider =
new DataProtectorTokenProvider<User>(provider.Create(user.Id));
// 3.
var provider = provider.Create("ConfirmUser");
manager.UserTokenProvider =
new DataProtectorTokenProvider<User>(provider);
// 4.
string raw = manager.GenerateEmailConfirmationToken(user.Id);
// remaining code removed
}
If you use this approach, be sure to use the same "ConfirmUser"
purposes argument during confirmation of the email.
What's inside IdentityHelper
?
Since the error is happening randomly, it occurs to me that the IdentityHelper
methods might be doing something funky to the code
that mucks up things. What's inside each of these methods?
IdentityHelper.GetUserConfirmationRedirectUrl()
IdentityHelper.RedirectToReturnUrl()
IdentityHelper.GetCodeFromRequest()
IdentityHelper.GetUserIdFromRequest()
I might write some tests to ensure that the raw code
that your process creates always matches the raw code
that your process retrieves from the Request
. In pseudo-code:
var code01 = CreateCode();
var code02 = UrlEncode(code01);
var request = CreateTheRequest(code02);
var response = GetTheResponse();
var code03 = GetTheCode(response);
var code04 = UrlDecode(code03);
Assert.AreEquals(code01, code04);
Run the above 10,000 times to ensure that no problems exist.
Conclusion
It's my strong suspicion that the problem lies in using one purposes
argument during token creation and another during confirmation. Use one purpose only and you might be fine.
Making this work on Azure Websites
- Use SqlCompact instead of localdb.
- Use
app.GetDataProtectionProvider()
notDpapiDataProtectionProvider
because Dpapi does not work with web farms.
Asp.NET - Identity 2 - Invalid Token Error - Php
Well, the error was caused by a code that was being execute after the token generation. This code was changing the SecurityStamp of the user in the database.
As the Asp.Net Identity 2 uses the the Security Stamp to generate and validate the token and it was changed after the generation of the token, the token was always invalid.
Thanks!
Token invalid on reset password with ASP.NET Identity
The token generated by UserManager
in ASP.NET Identity usually contains "+
" characters which when passed as a query string get changed into "" (a space) in the URL. In your ResetPassword ActionResult replace "
" with "
+
" like this:
var code = model.Code.Replace(" ", "+");
//And then change the following line
UserManager.ResetPasswordAsync(user.Id, model.Code, model.Password);
//To this one so it uses the code(spaces replaced with "+") instead of model.Code
UserManager.ResetPasswordAsync(user.Id, code, model.Password);
That should do the trick.
I had the same problem and found the answer here.
Related Topics
How to Access Internal Class Using Reflection
ASP.NET Identity 2 Giving "Invalid Token" Error
Webclient Accessing Page with Credentials
How to Use the Linqpad Dump() Extension Method in Visual Studio
How to Mock Static Methods in C# Using Moq Framework
Why Can't Yield Return Appear Inside a Try Block with a Catch
Is String.Format as Efficient as Stringbuilder
C# Conditional and (&&) or (||) Precedence
Hide Tab Header on C# Tabcontrol
How to Implement Smooth Scroll in a Wpf Listview
Getting Day Suffix When Using Datetime.Tostring()
Instantiate an Object with a Runtime-Determined Type
Ef Code First: How to Get Random Rows
How to Serialize Nested Properties to My Class in One Operation with JSON.Net