Недействительный токен удостоверения ASP.NET Core в электронном письме с подтверждением

Этот вопрос очень похож на этот недопустимый токен идентификатора aspnet в электронном письме с подтверждением но решения недействительны, потому что я использую новый ASP.NET Core 1.0, который включает ASP.NET Core Identity.

Мой сценарий таков:

  1. В задней части (ASP.NET Core) у меня есть функция, которая отправляет электронное письмо для сброса пароля со ссылкой. Чтобы сгенерировать эту ссылку, мне нужно сгенерировать код, используя Identity. Что-то вроде этого.

    public async Task SendPasswordResetEmailAsync(string email)
    {
        //_userManager is an instance of UserManager<User>
        var userEntity = await _userManager.FindByNameAsync(email);
        var tokenGenerated = await _userManager.GeneratePasswordResetTokenAsync(userEntity);
        var link = Url.Action("MyAction", "MyController", new { email = email, code = tokenGenerated }, protocol: HttpContext.Request.Scheme);
         //this is my service that sends an email to the user containing the generated password reset link
         await _emailService.SendPasswordResetEmailAsync(userEntity , link);
    }
    

    это приведет к созданию электронного письма со ссылкой на:

    http://myapp:8080/passwordreset?code=CfDJ8JBnWaVj6h1PtqlmlJaH57r9TRA5j7Ij1BVyeBUpqX+5Cq1msu9zgkuI32Iz9x/5uE1B9fKFp4tZFFy6lBTseDFTHSJxwtGu+jHX5cajptUBiVqIChiwoTODh7ei4+MOkX7rdNVBMhG4jOZWqqtZ5J30gXr/JmltbYxqOp4JLs8V05BeKDbbVO/Fsq5+jebokKkR5HEJU+mQ5MLvNURsJKRBbI3qIllj1RByXt9mufGRE3wmQf2fgKBkAL6VsNgB8w==

  2. Затем мое приложение AngularJs представит представление с формой для ввода и подтверждения нового пароля и ПОСТАВИТ объект JSON с новым паролем и кодом, полученным из параметра запроса в URL-адресе.

  3. Наконец, мой бэкенд получит запрос PUT, возьмет код и проверит его, используя Identity следующим образом:

    [HttpPut]
    [AllowAnonymous]
    [Route("api/password/{email}")]
    public async Task<IActionResult> SendPasswordEmailResetRequestAsync(string email, [FromBody] PasswordReset passwordReset)
    {
        //some irrelevant validatoins here
        await _myIdentityWrapperService.ResetPasswordAsync(email, passwordReset.Password, passwordReset.Code);
        return Ok();
    }
    

Проблема в том, что Identity отвечает

Неверный токен

ошибка. Я обнаружил, что проблема в том, что коды не совпадают, и приведенный выше код будет возвращен в объекте JSON в запросе PUT следующим образом:

CfDJ8JBnWaVj6h1PtqlmlJaH57r9TRA5j7Ij1BVyeBUpqX 5Cq1msu9zgkuI32Iz9x/5uE1B9fKFp4tZFFy6lBTseDFTHSJxwtGu jHX5cajptUBiVqIChiwoTODh7ei4 MOkX7rdNVBMhG4jOZWqqtZ5J30gXr/JmltbYxqOp4JLs8V05BeKDbbVO/Fsq5 jebokKkR5HEJU mQ5MLvNURsJKRBbI3qIllj1RByXt9mufGRE3wmQf2fgKBkAL6VsNgB8w==

Обратите внимание, что там, где было + символов, теперь есть символы пробелов , и, очевидно, это заставляет Identity думать, что токены разные. По какой-то причине Angular декодирует параметр запроса URL другим способом, который был закодирован.

Как решить эту проблему?


person diegosasw    schedule 05.08.2016    source источник
comment
В моем случае (Asp.Net Core 3.0) кажется, что на страницах с шаблоном возникла эта ошибка. См. Мой ответ здесь.   -  person jhhwilliams    schedule 27.10.2019


Ответы (9)


Этот ответ https://stackoverflow.com/a/31297879/2948212 указал мне правильное направление. Но, как я уже сказал, это было для другой версии, и теперь это немного другое решение.

Ответ все тот же: закодируйте токен в URL-адресе base 64, а затем декодируйте его в URL-адресе base 64. Таким образом, и Angular, и ASP.NET Core будут получать один и тот же код.

Мне нужно было установить еще одну зависимость для Microsoft.AspNetCore.WebUtilities;

Теперь код будет примерно таким:

public async Task SendPasswordResetEmailAsync(string email)
{
    //_userManager is an instance of UserManager<User>
    var userEntity = await _userManager.FindByNameAsync(email);
    var tokenGenerated = await _userManager.GeneratePasswordResetTokenAsync(userEntity);
    byte[] tokenGeneratedBytes = Encoding.UTF8.GetBytes(tokenGenerated);
    var codeEncoded = WebEncoders.Base64UrlEncode(tokenGeneratedBytes);
    var link = Url.Action("MyAction", "MyController", new { email = email, code = codeEncoded }, protocol: HttpContext.Request.Scheme);
     //this is my service that sends an email to the user containing the generated password reset link
     await _emailService.SendPasswordResetEmailAsync(userEntity , link);
}

и при получении кода обратно во время запроса PUT

[HttpPut]
[AllowAnonymous]
[Route("api/password/{email}")]
public async Task<IActionResult> SendPasswordEmailResetRequestAsync(string email, [FromBody] PasswordReset passwordReset)
{
    //some irrelevant validatoins here
    await _myIdentityWrapperService.ResetPasswordAsync(email, passwordReset.Password, passwordReset.Code);
    return Ok();
}

//in MyIdentityWrapperService
public async Task ResetPasswordAsync(string email, string password, string code)
{
    var userEntity = await _userManager.FindByNameAsync(email);
    var codeDecodedBytes = WebEncoders.Base64UrlDecode(code);
    var codeDecoded = Encoding.UTF8.GetString(codeDecodedBytes);
    await _userManager.ResetPasswordAsync(userEntity, codeDecoded, password);
}
person diegosasw    schedule 05.08.2016
comment
Это также работает, когда вы подтверждаете учетную запись электронной почты. - person NIMROD MAINA; 17.07.2018
comment
Спасибо! Кстати, вы могли бы подумать о том, чтобы SendPasswordEmailResetRequestAsync был украшен глаголом HttpPatch, потому что пользователь будет частично обновлен, а не полностью обновлен, где для PUT. - person Bernoulli IT; 10.09.2019

У меня была аналогичная проблема, и я кодировал свой токен, но он продолжал терпеть неудачу при проверке, и проблема оказалась следующей: options.LowercaseQueryStrings = true; Не устанавливайте true на options.LowercaseQueryStrings, это изменяет целостность токена проверки, и вы получите ошибку Invalid Token Error.

// This allows routes to be in lowercase
services.AddRouting(options =>
{
     options.LowercaseUrls = true;
      options.LowercaseQueryStrings = false;
});

person yaddly    schedule 19.10.2019

Я пробовал ответы выше, но это руководство помогло мне. По сути, вам нужно будет кодировать код, иначе вы столкнетесь с некоторыми странными ошибками. Подводя итог, вам нужно сделать это:

string code = HttpUtility.UrlEncode(UserManager.GenerateEmailConfirmationToken(userID));

После этого, если это применимо к вам, расшифруйте код:

string decoded = HttpUtility.UrlDecode(code)
person Andrey Seregin    schedule 03.07.2020
comment
Спасибо, чувак, круто и так элегантно. Этот ответ должен быть наверху, отлично работает! - person Mateech; 03.07.2020

После создания шаблона страницы ConfirmEmail в моем проекте Asp.Net Core 3.0 я столкнулся с той же проблемой.

Удаление следующей строки из метода OnGetAsync в ConfirmEmail.cshtml.cs устранило проблему:

code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));

На странице входа в шаблон code добавляется к callbackUrl, который затем кодируется URL-адресом с использованием HtmlEncoder.Default.Encode(callbackUrl). При щелчке по ссылке декодирование выполняется автоматически, и code как и должно быть, чтобы подтвердить электронное письмо.

ОБНОВЛЕНИЕ:

Я заметил, что во время процесса Забыли пароль code закодирован в Base64 перед помещением в callbackUrl, что означает, что декодирование Base64 IS необходимо.

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

code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));

Вот ссылка на исправленную проблему.

person jhhwilliams    schedule 27.10.2019
comment
Я тоже заметил эту проблему после, я вырубил ... это просто код, созданный с помощью скаффолда, или я пропустил эту ошибку, возникшую до того, как я выложил этот код? - person marno11; 14.11.2019
comment
Я тоже не уверен. Тест будет заключаться в создании пустого проекта, проверке ссылок, формировании шаблонов страниц и повторном тестировании ссылок. Я попробую и доложу. - person jhhwilliams; 14.11.2019
comment
@ marno11, я попытался воспроизвести проблему, используя пустой проект, но похоже, что проблема исправлена. Ссылки подтверждения и сброса пароля пустого проекта работали до создания шаблонов, а после создания шаблонов в файлах уже была строка Base64UrlEncode именно там, где мой ответ предлагает ее добавить. - person jhhwilliams; 15.11.2019
comment
Вот проблема, которая действительно была исправлена. - person jhhwilliams; 15.11.2019
comment
это отсортировало мой код проблемы = WebEncoders.Base64UrlEncode (Encoding.UTF8.GetBytes (code)); Спасибо - person Amr Elgarhy; 31.08.2020

У меня была такая же проблема при размещении моего веб-сайта с использованием Cloud Run в GCP, и ни одно из предложенных здесь решений не помогло мне.

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

Хранение ключей в каталоге /home/.aspnet/DataProtection-Keys, которые не могут храниться вне контейнера. Защищенные данные будут недоступны при уничтожении контейнера.

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

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

using Microsoft.AspNetCore.DataProtection;
...
public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection()
        .PersistKeysToDbContext<DbContext>();

    services.AddIdentity<User, IdentityRole>()
                .AddEntityFrameworkStores<AnotherDbContext>()
                .AddDefaultTokenProviders();
}

Подробную информацию о различных типах хранилищ можно найти здесь.

person jrojasledesma    schedule 26.06.2021

(согласно этому сообщению: https://stackoverflow.com/a/27943434/9869427)

Для проблемы с недействительным токеном resetPasswordAsync (диспетчер идентификационных данных) ... поскольку + стал пробелом в URL-адресе ... используйте Uri.EscapeUriString

Пример: в моем sendResetPasswordByMailAsync

var token = "Aa+Bb Cc";
var encodedToken = Uri.EscapeDataString(token); 

encodedToken = Aa% 20Bb2B% Cc

var url = $"http://localhost:4200/account/reset-password?email={email}&token={encodedToken}";
var mailContent= $"Please reset your password by <a href='{url}'>clicking here</a>.";

Теперь вы можете щелкнуть ссылку, и вы перейдете к правильному URL-адресу с помощью + (закодируйте% 2B) ... ваш токен не будет недействительным ...

person Community    schedule 29.08.2020

У меня была аналогичная проблема для ASP Core 2.1, и я чесал голову, потому что любое кодирование / декодирование для _1 _ (_ 2_) не сработало для меня. И у меня всегда была ошибка Недействительный токен для userManager.ConfirmEmailAsync(user, code)


РЕШЕНИЕ. Оказалось, что проблема заключалась в том, что пользователь был создан не с UserManager, а с использованием dbcontext, например _dbContext.Users.AddAsync, после замены этого метода создания на _userManager.CreateAsync у меня все работало нормально, даже без каких-либо кодирование / декодирование для _7 _ (_ 8_).

person user2771704    schedule 25.11.2020

В моем случае эта проблема возникла из-за метода OnPostAsync в RegisterModel, кодирующего URL-адрес обратного вызова:

var callbackUrl = Url.Page(
                    "/Account/ConfirmEmail",
                    pageHandler: null,
                    values: new { userId = user.Id, code = code },
                    protocol: Request.Scheme);

await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
                    $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");

Эта кодировка (приложение HtmlEncoder.Default.Encode () к callbackUrl) превратила URL-адрес '&' в '&', что сделало всю ссылку недействительной.

person Loaderon    schedule 01.12.2020

Вы также можете использовать регулярное выражение при проверке токена в сбросе.

var decode = token.Replace(" ", "+");

await _userManager.ResetPasswordAsync(user, decode, Password);
person LEarn Quik    schedule 26.05.2021