Подтвердить что ты не робот

Asp.NET Identity 2 выдает ошибку "Неверный токен"

Я использую Asp.Net-Identity-2 и пытаюсь проверить код подтверждения электронной почты, используя метод ниже. Но я получаю сообщение об ошибке "Неверный токен".

  • Диспетчер пользователей моего приложения выглядит следующим образом:

    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
    
  • Мое действие по созданию токена (и даже если я проверю токен здесь, я получу сообщение "Недопустимый токен"):

    [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
    
  • Мое действие по проверке токена (здесь я всегда получаю "Неверный токен" при проверке результата):

    [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
    

Я не знаю, чего не хватает или что не так...

4b9b3361

Ответ 1

Поскольку вы генерируете токен для пароля reset здесь:

string code = UserManager.GeneratePasswordResetToken(user.Id);

Но на самом деле пытается проверить токен для электронной почты:

result = await UserManager.ConfirmEmailAsync(id, code);

Это 2 разных токена.

В вашем вопросе вы говорите, что пытаетесь проверить электронную почту, но ваш код для пароля reset. Какой из них вы делаете?

Если вам требуется подтверждение по электронной почте, то сгенерируйте токен через

var emailConfirmationCode = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);

и подтвердите его с помощью

var confirmResult = await UserManager.ConfirmEmailAsync(userId, code);

Если вам нужен пароль reset, сгенерируйте токен следующим образом:

var code = await UserManager.GeneratePasswordResetTokenAsync(user.Id);

и подтвердите это следующим образом:

var resetResult = await userManager.ResetPasswordAsync(user.Id, code, newPassword);

Ответ 2

Я столкнулся с этой проблемой и решил ее. Есть несколько возможных причин.

1. Проблемы с кодированием URL (если проблема возникает "случайно")

Если это происходит случайно, у вас могут возникнуть проблемы с кодировкой URL. По неизвестным причинам токен не предназначен для защиты URL-адресов, что означает, что он может содержать недопустимые символы при передаче через URL-адрес (например, при отправке по электронной почте).

В этом случае следует использовать HttpUtility.UrlEncode(token) и HttpUtility.UrlDecode(token).

Как сказал в своих комментариях oão Pereira, UrlDecode не UrlDecode (или иногда нет?). Попробуйте оба, пожалуйста. Благодарю.

2. Несоответствующие методы (электронная почта против паролей)

Например:

    var code = await userManager.GenerateEmailConfirmationTokenAsync(user.Id);

а также

    var result = await userManager.ResetPasswordAsync(user.Id, code, newPassword);

Токен, сгенерированный поставщиком электронной почты, не может быть подтвержден провайдером сброса пароля-токена.

Но мы увидим причину, почему это происходит.

3. Различные экземпляры токенов

Даже если вы используете:

var token = await _userManager.GeneratePasswordResetTokenAsync(user.Id);

вместе с

var result = await _userManager.ResetPasswordAsync(user.Id, HttpUtility.UrlDecode(token), newPassword);

ошибка все еще может произойти.

Мой старый код показывает, почему:

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(...);
    }

а также:

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;
    }

Обратите внимание, что в этом коде каждый раз, когда создается UserManager (или new -ed), также создается новый dataProtectionProvider. Поэтому, когда пользователь получает электронное письмо и щелкает ссылку:

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");
    }

AccountController больше не является старым, _userManager и _userManager и его поставщик токенов. Таким образом, новый поставщик токенов потерпит неудачу, потому что у него нет этого токена в памяти.

Таким образом, нам нужно использовать один экземпляр для провайдера токенов. Вот мой новый код, и он отлично работает:

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;
    }

а также:

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;
        }
    }
}

Его нельзя назвать элегантным решением, но оно попало в корень и решило мою проблему.

Ответ 3

Я получал ошибку "Invalid Token" даже с кодом, подобным этому:

var emailCode = UserManager.GenerateEmailConfirmationToken(id);
var result = UserManager.ConfirmEmail(id, emailCode);

В моем случае проблема заключалась в том, что я создавал пользователя вручную и добавлял его в базу данных без использования UserManager.Create(...). Пользователь существовал в базе данных, но без отметки безопасности.

Интересно, что GenerateEmailConfirmationToken вернул токен, не пожаловавшись на отсутствие метки безопасности, но этот токен никогда не мог быть проверен.

Ответ 4

Кроме того, я видел, что сам код не работает, если он не закодирован.

Недавно я начал кодировать шахту следующим образом:

string code = manager.GeneratePasswordResetToken(user.Id);
code = HttpUtility.UrlEncode(code);

И затем, когда я буду готов его прочитать:

string code = IdentityHelper.GetCodeFromRequest(Request);
code = HttpUtility.UrlDecode(code);

Честно говоря, я удивлен, что в первую очередь это неправильно кодируется.

Ответ 5

В моем случае наше приложение AngularJS превратило все плюсовые знаки (+) в пустые места (""), поэтому токен действительно был недействительным, когда он был возвращен обратно.

Чтобы устранить эту проблему, в нашем методе ResetPassword в AccountController я просто добавил замену до обновления пароля:

code = code.Replace(" ", "+");
IdentityResult result = await AppUserManager.ResetPasswordAsync(user.Id, code, newPassword);

Надеюсь, это поможет кому-то еще работать с Identity в веб-API и AngularJS.

Ответ 6

string code = _userManager.GeneratePasswordResetToken(user.Id);

                code = HttpUtility.UrlEncode(code);

//отправить по электронной почте отдых


не декодировать код

var result = await _userManager.ResetPasswordAsync(user.Id, model.Code, model.Password); 

Ответ 7

Вот что я сделал: расшифруйте токен после кодирования его для URL (короче)

Сначала я должен был зашифровать созданный пользователь GenerateEmailConfirmationToken. (Стандартный совет выше)

    var token = await userManager.GenerateEmailConfirmationTokenAsync(user);
    var encodedToken = HttpUtility.UrlEncode(token);

и в вашем контроллере "Подтвердить" действие мне пришлось декодировать токен, прежде чем я его проверил.

    var decodedCode = HttpUtility.UrlDecode(mViewModel.Token);
    var result = await userManager.ConfirmEmailAsync(user,decodedCode);

Ответ 8

tl;dr: Зарегистрировать пользовательский токен-провайдер в aspnet core 2.2, чтобы использовать шифрование AES вместо защиты MachineKey, суть: https://gist.github.com/cyptus/dd9b2f90c190aaed4e807177c45c3c8b

Я столкнулся с той же проблемой с aspnet core 2.2, поскольку Чени указал, что экземпляры поставщика токенов должны быть одинаковыми. это не работает для меня, потому что

  • я получил different API-projects, который генерирует токен и получить токен для сброса пароля
  • API могут работать на different instances виртуальных машин, поэтому машинный ключ не будет же
  • API может restart и токен будет недействительным, потому что это больше не same instance

я мог бы использовать services.AddDataProtection().PersistKeysToFileSystem(new DirectoryInfo("path")) сохранить токен в файловой системе и избежать перезапуска и проблем с совместным использованием нескольких экземпляров, но не смог обойти проблему с несколькими проектами, поскольку каждый проект генерирует собственный файл.

для меня решение состоит в том, чтобы заменить логику защиты данных MachineKey собственной логикой, которая использует AES then HMAC для симметричного шифрования токена с помощью ключа из моих собственных настроек, который я могу использовать для всех машин, экземпляров и проектов. Я взял логику шифрования от Зашифровать и расшифровать строку в С#? (Суть: https://gist.github.com/jbtule/4336842#file-aesthenhmac-cs) и реализовал собственный TokenProvider:

    public class AesDataProtectorTokenProvider<TUser> : DataProtectorTokenProvider<TUser> where TUser : class
    {
        public AesDataProtectorTokenProvider(IOptions<DataProtectionTokenProviderOptions> options, ISettingSupplier settingSupplier)
            : base(new AesProtectionProvider(settingSupplier.Supply()), options)
        {
            var settingsLifetime = settingSupplier.Supply().Encryption.PasswordResetLifetime;

            if (settingsLifetime.TotalSeconds > 1)
            {
                Options.TokenLifespan = settingsLifetime;
            }
        }
    }
    public class AesProtectionProvider : IDataProtectionProvider
    {
        private readonly SystemSettings _settings;

        public AesProtectionProvider(SystemSettings settings)
        {
            _settings = settings;

            if(string.IsNullOrEmpty(_settings.Encryption.AESPasswordResetKey))
                throw new ArgumentNullException("AESPasswordResetKey must be set");
        }

        public IDataProtector CreateProtector(string purpose)
        {
            return new AesDataProtector(purpose, _settings.Encryption.AESPasswordResetKey);
        }
    }
    public class AesDataProtector : IDataProtector
    {
        private readonly string _purpose;
        private readonly SymmetricSecurityKey _key;
        private readonly Encoding _encoding = Encoding.UTF8;

        public AesDataProtector(string purpose, string key)
        {
            _purpose = purpose;
            _key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
        }

        public byte[] Protect(byte[] userData)
        {
            return AESThenHMAC.SimpleEncryptWithPassword(userData, _encoding.GetString(_key.Key));
        }

        public byte[] Unprotect(byte[] protectedData)
        {
            return AESThenHMAC.SimpleDecryptWithPassword(protectedData, _encoding.GetString(_key.Key));
        }

        public IDataProtector CreateProtector(string purpose)
        {
            throw new NotSupportedException();
        }
    }

и поставщик настроек, который я использую в своем проекте для предоставления своих настроек

    public interface ISettingSupplier
    {
        SystemSettings Supply();
    }

    public class SettingSupplier : ISettingSupplier
    {
        private IConfiguration Configuration { get; }

        public SettingSupplier(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public SystemSettings Supply()
        {
            var settings = new SystemSettings();
            Configuration.Bind("SystemSettings", settings);

            return settings;
        }
    }

    public class SystemSettings
    {
        public EncryptionSettings Encryption { get; set; } = new EncryptionSettings();
    }

    public class EncryptionSettings
    {
        public string AESPasswordResetKey { get; set; }
        public TimeSpan PasswordResetLifetime { get; set; } = new TimeSpan(3, 0, 0, 0);
    }

наконец зарегистрируйте провайдера в автозагрузке:

 services
     .AddIdentity<AppUser, AppRole>()
     .AddEntityFrameworkStores<AppDbContext>()
     .AddDefaultTokenProviders()
     .AddTokenProvider<AesDataProtectorTokenProvider<AppUser>>(TokenOptions.DefaultProvider);


 services.AddScoped(typeof(ISettingSupplier), typeof(SettingSupplier));
//AESThenHMAC.cs: See https://gist.github.com/jbtule/4336842#file-aesthenhmac-cs

Ответ 9

Убедитесь, что при генерации вы используете:

GeneratePasswordResetTokenAsync(user.Id)

И подтвердите, что вы используете:

ResetPasswordAsync(user.Id, model.Code, model.Password)

Если вы используете методы сопоставления, но он все равно не работает, убедитесь, что user.Id в обоих методах одинаково. (Иногда ваша логика может быть неправильной, потому что вы разрешаете использовать тот же адрес электронной почты для реестра и т.д.)

Ответ 10

Возможно, это старый поток, но, только для случая, я почесывал голову случайным появлением этой ошибки. Я проверяю все темы и проверяю каждое предложение, но, как ни странно, некоторые из кодов, возвращаемых как "недопустимый токен". После некоторых запросов к пользовательской базе данных я, наконец, обнаружил, что эти ошибки "недопустимого токена", которые напрямую связаны с пробелами или другими алфавитно-цифровыми символами в именах пользователей. Тогда решение было легко найти. Просто настройте UserManager, чтобы эти символы были введены в имена пользователей. Это можно сделать сразу после того, как пользовательский менеджер создаст событие, добавив новый параметр UserValidator в значение false следующим свойством:

 public static UserManager<User> Create(IdentityFactoryOptions<UserManager<User>> options, IOwinContext context)
    {
        var userManager = new UserManager<User>(new UserStore());

        // this is the key 
        userManager.UserValidator = new UserValidator<User>(userManager) { AllowOnlyAlphanumericUserNames = false };


        // other settings here
        userManager.UserLockoutEnabledByDefault = true;
        userManager.MaxFailedAccessAttemptsBeforeLockout = 5;
        userManager.DefaultAccountLockoutTimeSpan = TimeSpan.FromDays(1);

        var dataProtectionProvider = options.DataProtectionProvider;
        if (dataProtectionProvider != null)
        {
            userManager.UserTokenProvider = new DataProtectorTokenProvider<User>(dataProtectionProvider.Create("ASP.NET Identity"))
            {
                TokenLifespan = TimeSpan.FromDays(5)
            };
        }

        return userManager;
    }

Надеюсь, это поможет "поздним приходам", как я!

Ответ 11

Убедитесь, что токен, который вы генерируете, не истекает быстро - я изменил его на 10 секунд для тестирования и всегда возвращал ошибку.

    if (dataProtectionProvider != null) {
        manager.UserTokenProvider =
           new DataProtectorTokenProvider<AppUser>
              (dataProtectionProvider.Create("ConfirmationToken")) {
               TokenLifespan = TimeSpan.FromHours(3)
               //TokenLifespan = TimeSpan.FromSeconds(10);
           };
    }

Ответ 12

Мы столкнулись с такой ситуацией с группой пользователей, где все работало нормально. Мы изолировали его от системы защиты электронной почты Symantec, которая заменяет ссылки в наших электронных письмах пользователям безопасными ссылками, которые переходят на их сайт для проверки, а затем перенаправляет пользователя на исходную ссылку, которую мы отправили.

Проблема в том, что они вводят декодирование... они, кажется, выполняют URL-кодирование на сгенерированной ссылке, чтобы встроить нашу ссылку в качестве параметра запроса на свой сайт, но затем, когда пользователь нажимает и clickksafe.symantec.com декодирует его URL-адрес. декодирует первую часть, необходимую для кодирования, а также содержимое нашей строки запроса, а затем URL-адрес, на который перенаправляется браузер, был декодирован, и мы вернулись в состояние, когда специальные символы портят обработку строки запроса в коде,

Ответ 13

Здесь у меня та же проблема, но спустя много времени я обнаружил, что в моем случае ошибка неверного токена была вызвана тем фактом, что мой пользовательский класс Account имеет свойство Id, повторно объявленное и переопределенное.

Как это:

 public class Account : IdentityUser
 {
    [ScaffoldColumn(false)]
    public override string Id { get; set; } 
    //Other properties ....
 }

Чтобы исправить это, я просто удалил это свойство и снова сгенерировал схему базы данных, чтобы быть уверенным.

Удаление этого решает проблему.

Ответ 14

В моем случае мне просто нужно сделать HttpUtility.UrlEncode перед отправкой электронного письма. Нет HttpUtility.UrlDecode во время сброса.

Ответ 15

Моя проблема заключалась в том, что в электронном письме содержалась опечатка, содержащая ConfirmationToken:

<p>Please confirm your account by <a [email protected]'>clicking here</a>.</p>

Это означало, что дополнительный апостроф был добавлен в конец Подтверждения.

D'о!

Ответ 16

Моя проблема заключалась в том, что мне не хватало <input asp-for="Input.Code" type="hidden"/> в форме сброса пароля

<form role="form" method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<input asp-for="Input.Code" type="hidden" />

Ответ 17

Связанные с Ченни 3. Различные экземпляры токенов-провайдеров.

В моем случае я передавал IDataProtectionProvider.Create новый guid каждый раз, когда он вызывался, что препятствовало распознаванию существующих кодов при последующих вызовах web api (каждый запрос создает свой собственный диспетчер пользователей).

Создание статической строки решило это для меня.

private static string m_tokenProviderId = "MyApp_" + Guid.NewGuid().ToString();
...
manager.UserTokenProvider =
  new DataProtectorTokenProvider<User>(
  dataProtectionProvider.Create(new string[1] { m_tokenProviderId } ))
  {
      TokenLifespan = TimeSpan.FromMinutes(accessTokenLifespan)
  };