Asp.NET Identity 2 giving “Invalid Token” error

前端 未结 21 1919
情话喂你
情话喂你 2020-11-27 03:04

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\"

21条回答
  •  情深已故
    2020-11-27 03:27

    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 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
    {
        private static readonly UserStore UserStore = new UserStore();
        private static readonly UserManager Instance = new UserManager();
    
        private UserManager()
            : base(UserStore)
        {
        }
    
        public static UserManager CreateUserManager()
        {
            var dataProtectionProvider = new DpapiDataProtectionProvider();
            Instance.UserTokenProvider = new DataProtectorTokenProvider(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 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
    {
        private static readonly UserStore UserStore = new UserStore();
        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 _tokenProvider;
    
        public static DataProtectorTokenProvider Provider
        {
            get
            {
    
                if (_tokenProvider != null)
                    return _tokenProvider;
                var dataProtectionProvider = new DpapiDataProtectionProvider();
                _tokenProvider = new DataProtectorTokenProvider(dataProtectionProvider.Create());
                return _tokenProvider;
            }
        }
    }
    

    It could not be called an elegant solution, but it hit the root and solved my problem.

提交回复
热议问题