29

I'm writting MVC 5 and using Identity 2.0.

Now I m trying to reset password. But i always getting "invalid token" error for reset password token.

    public class AccountController : Controller
{
    public UserManager<ApplicationUser> UserManager { get; private set; }

    public AccountController()
        : this(new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext())))
    {
    }

and i set DataProtectorTokenProvider,

        public AccountController(UserManager<ApplicationUser> userManager)
    {   
        //usermanager config
        userManager.PasswordValidator = new PasswordValidator { RequiredLength = 5 };  
        userManager.EmailService = new IddaaWebSite.Controllers.MemberShip.MemberShipComponents.EmailService(); 

        var provider = new Microsoft.Owin.Security.DataProtection.DpapiDataProtectionProvider();
        userManager.UserTokenProvider = new Microsoft.AspNet.Identity.Owin.DataProtectorTokenProvider<ApplicationUser>(provider.Create("UserToken"))
                                                    as IUserTokenProvider<ApplicationUser, string>;




        UserManager = userManager;

    }

i generate password reset before sending mail

 [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> ManagePassword(ManageUserViewModel model)
    {
        if (Request.Form["email"] != null)
        {
          var email = Request.Form["email"].ToString();
          var user = UserManager.FindByEmail(email);
          var token = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
           //mail send
        }
   }

i click link in mail and i'm getting passwordreset token and using

var result = await UserManager.ResetPasswordAsync(model.UserId, model.PasswordToken, model.NewPassword);

the result always false and it says "Invalid Token". Where should i fix ?

erkan demir
  • 1,386
  • 4
  • 20
  • 38
  • Where are you storing your generated tokens at? – Sefa Dec 23 '14 at 10:09
  • 2
    @vgSefa In Identity the tokens are signed, means there's no need to store them somewhere. *Erkan*: Is a token generated? I don't think that the cast is necessary when setting the **UserTokenProvider**. Maybe something goes wrong there. – Horizon_Net Dec 23 '14 at 10:15
  • @Horizon_Net yes it genereted and try to remove castin. it still doesnt work – erkan demir Dec 23 '14 at 11:27
  • Just another idea: Is the email address of the appropriate user already confirmed? As mentioned [here](http://www.asp.net/identity/overview/features-api/account-confirmation-and-password-recovery-with-aspnet-identity): *The method fails silently if the user email has not been confirmed. If an error was posted for an invalid email address, malicious users could use that information to find valid userId (email aliases) to attack.* – Horizon_Net Dec 23 '14 at 11:58
  • yes good point, thank you. i added email confirmed condition. – erkan demir Dec 23 '14 at 12:01
  • Is your token UrlEncoded when you pass it in the email? I've seen many times where lack of UrlEncoding is causing issues. I mean compare what token you get generated to the one that you get from email - are they identical? – trailmax Dec 23 '14 at 12:52

4 Answers4

38

UserManager.GeneratePasswordResetTokenAsync() very often returns string that contains '+' characters. If you pass parameters by query string, this is the cause ('+' character is a space in query string in URL).

Try to replace space characters in model.PasswordToken with '+' characters.

Mateusz Cisek
  • 775
  • 11
  • 22
  • Plus, append "==" add the end of the token in my case. – user1874435 Mar 24 '15 at 21:39
  • 3
    Or use WebUtility.UrlEncode(code) when sending the mail and WebUtility.UrDecode(Model.code) when handling the form – edosoft Jul 06 '16 at 19:49
  • Be also aware of this answer: http://stackoverflow.com/a/13095475/453142 it was my case!! – David Létourneau Aug 16 '16 at 22:28
  • i also the same issue i can't getting '==' in the last end from code so how can handle this i am code send via query string in the mail and i m also try this WebUtility.UrlEncode(code) in the mail send and getting time WebUtility.UrDecode(Model.code) using this but still getting issue and end of getting invalid token. – coderwill Apr 10 '17 at 12:10
  • I also had problems with encoding and decoding. It helped me to first encode the token as Base64 and then decode it again when reading it in. For encoding / decoding the string see https://stackoverflow.com/questions/11743160/how-do-i-encode-and-decode-a-base64-string – Vigi Tri Oct 20 '21 at 05:48
21
[HttpPost]
[ValidateAntiForgeryToken]
publicasync Task<ActionResult> ManagePassword(ManageUserViewModel model)
{
    if (Request.Form["email"] != null)
    {
      var email = Request.Form["email"].ToString();
      var user = UserManager.FindByEmail(email);
      var token = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
       //before send mail
      token = HttpUtility.UrlEncode(token);   
  //mail send

    }
}

And on password reset action decode token HttpUtility.UrlDecode(token);

TotPeRo
  • 6,561
  • 4
  • 47
  • 60
Tebogo Johannes
  • 301
  • 3
  • 4
  • 1
    Don't forget to decode the token on reset like this: ```var result = await UserManager.ResetPasswordAsync(user.Id, HttpUtility.UrlDecode(code), user.Password);``` – TotPeRo May 02 '15 at 22:44
  • This encoding/decoding was the part that was missing in my project and because of that my tokens sent via email wrer not working. Thanks! – Koscik May 26 '15 at 08:08
13

I have found that the 'Invalid Token' error also occurs when the SecurityStamp column is NULL for the user in the AspNetUsers table in the database. The SecurityStamp won't be NULL with the out-of-the-box MVC 5 Identity 2.0 code, however a bug had been introduced in our code when doing some customization of the AccountController that cleared out the value in the SecurityStamp field.

stonefree
  • 400
  • 1
  • 3
  • 8
  • Updating the security stamp can fail when unique e-mail address enforcement is enabled and a user is manually inserted that violates that setting. – markshiz Sep 01 '16 at 17:19
0

Many answers here URLEncode the token before sending to get around the fact that the token (being a base 64 encoded string) often contains the '+' character. Solutions must also take into account that the token ends with '=='.

I was struggling with this issue & it turns out many users within a large organisation were using Scanmail Trustwave Link Validator(r) which was not symmetrically encoding and decoding URLEncoded stings in the email link (at the time of writing).

The easiest way was to use Mateusz Cisek's answer and send a non URLEncoded token and simply replace the space characters back to +. In my case this was done in an angular SPA so the Javascript becomes $routeParams.token.replace(/ /g,'+').

The caveat here will be if using AJAX to send the token and rolling your own query string parsing algorithm - many examples split each parameter on '=', which will of course not include the '==' at the end of the token. Easy to work around by using one of the regex solutions or looking for the 1st '=' only.

Brent
  • 4,611
  • 4
  • 38
  • 55