Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Asp.NET Identity 2 giving "Invalid Token" error

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...

like image 976
Julio Schurt Avatar asked Aug 20 '14 12:08

Julio Schurt


People also ask

How do I fix an invalid token?

There are two ways to fix the error: (RECOMMENDED) Change the application signature algorithm to RS256 instead of HS256. Change the value of your responseType parameter to token id_token (instead of the default), so that you receive an access token in the response.

Why is my token saying invalid?

If you're trying to reset your password and you receive an error citing an “invalid token” or asking you for your token, it's likely that the link you clicked on to reset your password has expired. For security reasons, passwords are never sent out across the Internet.

Why token is not working?

When the Token is functioning properly, the green light should be blinking once every minute. If you see a red blinking light, or when there is no light, this means the Token is not working. You need to replace your Token if it is lost, faulty, damaged, or when the battery is low.

What is security stamp in asp net identity?

The security stamp is a Guid stored in the database against the user. It gets updated when certain actions take place within the Identity UserManager class and provides a way to invalidate old tokens when an account has changed.


2 Answers

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.

like image 129
cheny Avatar answered Sep 17 '22 17:09

cheny


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); 
like image 39
trailmax Avatar answered Sep 21 '22 17:09

trailmax