Don't miss out Virtual Happy Hour this Friday (April 26).

Try our conversational search powered by Generative AI!

Password Reset keeps returning 'invalid token'

Vote:
 

Hello,

I'm migrating our newly updated CMS10/Commerce website from using the old .net Membership references to Episerver's aspnetidentity. Everything is going pretty well but I've hit a snag with Identity's change password functionality.  No matter what, it keeps telling me that the token I generated is invalid.

Here is my UserManager:

    public class CustomUserManager : ApplicationUserManager
    {
        private static readonly CustomUserManager Instance = new CustomUserManager();

        public CustomUserManager() : base(new UserStore(new ApplicationDbContext()))
        {
            this.PasswordHasher = new SQLPasswordHasher();
        }

        public static CustomUserManager Create()
        {

            var provider = new DpapiDataProtectionProvider("EpiseverSite");
            Instance.UserTokenProvider = TokenProvider.Provider;

            return Instance;
        }

    }

Here is the TokenProvider class:

    public static class TokenProvider
    {

        private static DataProtectorTokenProvider _tokenProvider;

        public static DataProtectorTokenProvider Provider
        {
            get
            {

                if (_tokenProvider != null)
                    return _tokenProvider;
                var dataProtectionProvider = new DpapiDataProtectionProvider();
                _tokenProvider = new DataProtectorTokenProvider(dataProtectionProvider.Create());
                return _tokenProvider;
            }
        }
    }

Here is the code that generates and sends the reset password link to the user:

        public async Task Post(ForgotPasswordPage currentPage, [FromBody] ForgotPasswordPostbackData ForgotPasswordPostbackData)
        {
            var model = new ForgotPasswordModel(currentPage);

            var user = await _userManager.FindByNameAsync(ForgotPasswordPostbackData.Email);
            if (user != null)
            {
                var token = _userManager.GeneratePasswordResetTokenAsync(user.Id);
                string resetLink = "Click this link to reset your password.";
                string subject = "Reset your password";
                string body = "You are receiving this email because you requested to reset your password for our site. Please " + resetLink;
                string from = "info@redacteddomain.com";

                MailMessage message = new MailMessage(from, user.Email);
                message.Subject = subject;
                message.Body = body;
                SmtpClient client = new SmtpClient();

                // Attempt to send the email
                try
                {
                    client.Send(message);
                    model.Message = "Thank you, an e-mail with instructions on how to change your password has been sent to " + user.Email;
                }
                catch (Exception e)
                {
                    model.Message = "Failed to send password reset email.  Please contact support. " + e.Message;
                    return View("Index", model);
                }
            }
            else
            {
                model.Message = "The e-mail address you provided does not exist in our system.  Please retype your e-mail address and try again.";
            }

            return View("Index", model);
        }

And lastly, here is the code for the change password page that the user gets a link to in their e-mail.  the variable "rt" is the reset token:

    public class ResetUserPasswordController : ContentController
    {

        private readonly CustomUserManager _userManager = CustomUserManager.Create();

        [System.Web.Mvc.AllowAnonymous]
        public ActionResult Index(ResetUserPasswordPage currentPage, SitePageData sitePageData, string userId, string rt)
        {
            //If token or userid not found, redirect to login page.
            if(userId == null || rt == null)
            {
                Response.Redirect("/my-account/login");
            }

            ViewBag.CurrentPage = sitePageData;
            var model = new ResetUserPasswordModel(currentPage);
            model.ResetUserPasswordPostbackData.userId = userId;
            model.ResetUserPasswordPostbackData.rt = rt;
            return View(model);
            
        }

        [System.Web.Mvc.HttpPost]
        public async Task Post(ResetUserPasswordPage currentPage, [FromBody] ResetUserPasswordPostbackData resetUserPasswordPostbackData)
        {

            var model = new ResetUserPasswordModel(currentPage);

            if (ModelState.IsValid)
            {

                IdentityResult result;
                try
                {
                    result = await _userManager.ResetPasswordAsync(resetUserPasswordPostbackData.userId, resetUserPasswordPostbackData.rt, resetUserPasswordPostbackData.PasswordConfirm);

                    if (result.Succeeded)
                    {
                        model.Message = "Your password has been reset.  Return to the login page to log in with your new credentials.";
                        return View(model);
                    }
                    else
                    {
                        model.Message = result.Errors.FirstOrDefault();
                    }
                }
                
                catch (InvalidOperationException ioe)
                {
                    Response.Redirect("/my-account/login");
                }


            }

            return View("Index", model);
        }

    }

I've confirmed that both userid and the token are not null and match what was sent as url parameters in the link sent to the user.  Can anyone tell me what I might be missing?  I'm new to .NET Identity.

Thanks in advance!

John

#183266
Oct 09, 2017 22:37
Vote:
 

Just an update, I've resolved this.  In case anyone runs into this, it turns out that the token was not URL-friendly, and so I had to wrap it in a HttpUtility.UrlEncode() like so:

string resetLink = "<a href='http://www.redacteddomain.com/my-account/reset-password/?rt=" + HttpUtility.UrlEncode(token.Result) + "&userId=" + user.Id + "'>Click this link to reset your password.</a>";

After that it worked just fine.

#183293
Oct 10, 2017 17:29
This topic was created over six months ago and has been resolved. If you have a similar question, please create a new topic and refer to this one.
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.