Search code examples
asp.net-mvcasp.net-identityasp.net-identity-2

Asp.net Identity - token is not matching encoding issue?


I am trying to use asp.net identity for authentication, I am having some issues with encoding/decoding.

User clicks on forgot password link, so we call out:

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
[PassModelStateToTempData]
public async Task<ActionResult> ForgotPassword(ForgotPasswordViewModel model)
{
    if (ModelState.IsValid)
    {
        logger.Info("reset_password attempting for {0}", model.Email);

        var user = await UserManager.FindByNameAsync(model.Email);
        if (user == null || !(await UserManager.IsEmailConfirmedAsync(user.Id)))
        {
            this.Flash("Please check your email, we have sent you instructions on how to reset your password");
            return RedirectToAction("ForgotPassword");
        }
        string code = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
        logger.Debug("forgot_password code {0}", code);
        var callbackUrl = Url.Action("ResetPassword", "Session", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
        messagingService.ResetPassword(user.Email, callbackUrl);

        this.Flash("Please check your email, we have sent you instructions on how to reset your password");
        logger.Debug("remind_me successfully send out email to {0} {1}", model.Email, callbackUrl);
        return RedirectToAction("ForgotPassword");
    }

    logger.Info("reset_password failed for {0}", model.Email);

    // If we got this far, something failed, redisplay form
    return RedirectToAction("ForgotPassword");
}

User gets email then clicks link so we run:

[HttpGet]
[AllowAnonymous]
public ActionResult ResetPassword(string code)
{
    if (code == null)
    {
        this.Flash("Invalid login token, please enter your email address again");
        return RedirectToAction("ForgotPassword");
    }
    var vm = new ResetPasswordViewModel
    {
         Code = code
    };
    return View(vm);
}

We pass on token into view - we ask for email and password, then user hits post and we run:

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ResetPassword(ResetPasswordViewModel model)
{
    if (!ModelState.IsValid)
    {
        return RedirectToAction("ResetPassword");
    }

    var user = await UserManager.FindByNameAsync(model.Email);
    if (user == null)
    {
        logger.Info("reset_password user not found [{0}]", model.Email);
        // Don't reveal that the user does not exist
        return RedirectToAction("ResetPasswordConfirmation", "Session");
    }
    var result = await UserManager.ResetPasswordAsync(user.Id, model.Code, model.Password);

    if (result.Succeeded)
    {
        return RedirectToAction("ResetPasswordConfirmation", "Session");
    }
    AddErrors(result);
    return RedirectToAction("ResetPassword", new { code = model.Code });
}

For some reason tokens seem to not match, here are an example of the token I am getting - why the case difference?

Token:

2015-10-14 13:06:52.7545|DEBUG|Controllers.Application|forgot_password code BoUZZ9OS7rEkKMkEJzerWdds4dZLHFTHO/EkjQC2Zr8YJvCyjsXUKBRLZk8jmAqhjyxOzgqOLdJ8P/ji8y+om2ne7bcsLICzcdLSHzrP6BNEr1/+HKvHcYan+JzAX7Ifpgq7casmMj4f9esAdxejLA==

Notice the case difference:

2015-10-14 13:07:29.7164|INFO|Controllers.Application|reset_password attempting for my.email@gmail.com with token: bouzz9os7rekkmkejzerwdds4dzlhftho/ekjqc2zr8yjvcyjsxukbrlzk8jmaqhjyxozgqoldj8p/ji8y+om2ne7bcsliczcdlshzrp6bner1/+hkvhcyan+jzax7ifpgq7casmmj4f9esadxejla== -> Invalid token.


Solution

  • Your MVC routing is set up to generate lowercase URLs:

    routes.LowercaseUrls = true;
    

    This means that your codes are also being converted to lowercase. Possible solutions are:

    1. Turn off LowercaseUrls if you can (or want)
    2. Use MVC attribute routing, though this can be quite a switch.
    3. The simplest option for you may be to simply create the URL yourself:

      //Generate the URL without the code parameter
      var callbackUrl = Url.Action(
          "ResetPassword",
          "Session", 
          new { userId = user.Id }, 
          protocol: Request.Url.Scheme);
      
      //Manually add the code, remembering to encode it
      callbackUrl = callbackUrl + "&code=" HttpUtility.UrlEncode(code);