Asp.NET Identity 2 com erro “Token Inválido”

Estou usando o Asp.Net-Identity-2 e estou tentando verificar o código de verificação de e-mail usando o método abaixo. Mas estou recebendo uma mensagem de erro “Token inválido” .

  • O Gerenciador de usuários do meu aplicativo é assim:

    public class AppUserManager : UserManager { public AppUserManager(IUserStore store) : base(store) { } public static AppUserManager Create(IdentityFactoryOptions options, IOwinContext context) { AppIdentityDbContext db = context.Get(); AppUserManager manager = new AppUserManager(new UserStore(db)); manager.PasswordValidator = new PasswordValidator { RequiredLength = 6, RequireNonLetterOrDigit = false, RequireDigit = false, RequireLowercase = true, RequireUppercase = true }; manager.UserValidator = new UserValidator(manager) { AllowOnlyAlphanumericUserNames = true, RequireUniqueEmail = true }; var dataProtectionProvider = options.DataProtectionProvider; //token life span is 3 hours if (dataProtectionProvider != null) { manager.UserTokenProvider = new DataProtectorTokenProvider (dataProtectionProvider.Create("ConfirmationToken")) { TokenLifespan = TimeSpan.FromHours(3) }; } manager.EmailService = new EmailService(); return manager; } //Create } //class } //namespace 
  • Minha ação para gerar o token é (e mesmo se eu verificar o token aqui, recebo a mensagem “Token inválido”):

     [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: link"); //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 
  • Minha ação para verificar o token é (aqui, eu sempre recebo “Token inválido” quando eu verifico o resultado):

     [AllowAnonymous] public async Task 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:

  • " + ioe.Message + "
  • " }); } 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 += "
  • " + strError + "
  • "; } //foreach return View("Error", new string[] { strErrorMsg }); } //ForgotPasswordConfirmation

Eu não sei o que pode estar faltando ou o que está errado …

Porque você está gerando token para redefinição de senha aqui:

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

Mas, na verdade, tentando validar o token para o email:

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

Estes são dois tokens diferentes.

Em sua pergunta, você diz que está tentando confirmar o e-mail, mas seu código é para redefinição de senha. Qual você está fazendo?

Se você precisar de confirmação por e-mail, gere o token via

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

e confirme via

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

Se você precisar redefinir a senha, gere um token assim:

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

e confirme assim:

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

Eu encontrei este problema e resolvi isso. Existem várias razões possíveis.

1. Problemas de Codificação de URL (se o problema ocorrer “aleatoriamente”)

Se isso acontecer aleatoriamente, talvez você esteja encontrando problemas de codificação de URL. Por motivos desconhecidos, o token não foi projetado para segurança de url, o que significa que ele pode conter caracteres inválidos ao ser passado por um URL (por exemplo, se enviado por email).

Nesse caso, HttpUtility.UrlEncode(token) e HttpUtility.UrlDecode(token) devem ser usados.

Como o oão Pereira disse em seus comentários, o UrlDecode não é (ou às vezes não é) obrigatório. Tente os dois por favor. Obrigado.

2. Métodos não correspondentes (email vs tokens de senha)

Por exemplo:

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

e

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

O token gerado pelo fornecimento de token de email não pode ser confirmado pelo provedor de token de senha de redefinição.

Mas vamos ver a causa raiz do porque isso acontece.

3. Instâncias diferentes de provedores de token

Mesmo se você estiver usando:

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

junto com

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

o erro ainda pode acontecer.

Meu código antigo mostra porque:

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

e:

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

Preste atenção que neste código, toda vez que um UserManager é criado (ou new ), um novo dataProtectionProvider é gerado também. Então, quando um usuário recebe o email e clica no 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"); } 

O AccountController não é mais o antigo e nem o _userManager e seu provedor de tokens. Portanto, o novo provedor de tokens falhará porque não possui esse token em sua memory.

Portanto, precisamos usar uma única instância para o provedor de token. Aqui está o meu novo código e funciona bem:

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

e:

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

Não poderia ser chamado de uma solução elegante, mas atingiu a raiz e resolveu o meu problema.

Eu estava recebendo o erro “Token Inválido” mesmo com código como este:

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

No meu caso, o problema era que eu estava criando o usuário manualmente e adicionando-o ao database sem usar o método UserManager.Create(...) . O usuário existia no database, mas sem um carimbo de segurança.

É interessante que o GenerateEmailConfirmationToken retornou um token sem reclamar da falta de carimbo de segurança, mas esse token nunca pôde ser validado.

Além disso, vi o próprio código falhar se não estiver codificado.

Eu comecei recentemente a codificar a minha da seguinte forma:

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

E quando eu estiver pronto para ler de volta:

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

Para ser sincero, estou surpreso que não esteja sendo devidamente codificado em primeiro lugar.

No meu caso, nosso aplicativo AngularJS converteu todos os sinais de mais (+) em espaços vazios (“”), portanto, o token era realmente inválido quando foi passado de volta.

Para resolver o problema, em nosso método ResetPassword no AccountController, simplesmente adicionei uma substituição antes de atualizar a senha:

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

Espero que isso ajude qualquer pessoa que trabalhe com o Identity em uma API da Web e AngularJS.

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

// enviar email de descanso


não decodifique o código

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

Talvez este seja um segmento antigo, mas, apenas para o caso, tenho coçado a cabeça com a ocorrência aleatória desse erro. Eu tenho checado todos os tópicos e verificado cada sugestão, mas – aleatoriamente parecia – alguns dos códigos que retornaram como “token inválido”. Depois de algumas consultas ao database do usuário, finalmente descobri que os erros de “token inválido” estavam diretamente relacionados a espaços ou outros caracteres não alfanuméricos em nomes de usuários. A solução foi fácil de encontrar. Basta configurar o UserManager para permitir esses caracteres nos nomes dos usuários. Isso pode ser feito logo após o gerenciador de usuários criar um evento, adicionando uma nova configuração de UserValidator para false a propriedade correspondente desta maneira:

  public static UserManager Create(IdentityFactoryOptions> options, IOwinContext context) { var userManager = new UserManager(new UserStore()); // this is the key userManager.UserValidator = new UserValidator(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(dataProtectionProvider.Create("ASP.NET Identity")) { TokenLifespan = TimeSpan.FromDays(5) }; } return userManager; } 

Espero que isso possa ajudar “chegadas tardias” como eu!

Certifique-se quando gerar, você usa:

 GeneratePasswordResetTokenAsync(user.Id) 

E confirme que você usa:

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

Se você se certificar de que está usando os methods de correspondência, mas ainda assim não funcionar, verifique se user.Id é o mesmo em ambos os methods. (Às vezes, sua lógica pode não estar correta porque você permite usar o mesmo e-mail para registro, etc.)

Certifique-se de que o token que você gera não expire rapidamente – eu o alterei para 10 segundos para teste e sempre retornaria o erro.

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

Nós nos deparamos com esta situação com um conjunto de usuários onde tudo estava funcionando bem. Nós o isolamos do sistema de proteção de e-mail da Symantec, que substitui os links em nossos e-mails para os usuários com links seguros que acessam o site para validação e redirecionam o usuário para o link original que enviamos.

O problema é que eles estão introduzindo um decodificador … eles parecem fazer um URL Encode no link gerado para incorporar nosso link como um parâmetro de consulta ao seu site, mas quando o usuário clica e clica em “seguro”, ele decodifica o URL decodifica a primeira parte que precisou codificar, mas também o conteúdo de nossa string de consulta e, em seguida, a URL para a qual o navegador foi redirecionado foi decodificada e estamos de volta ao estado em que os caracteres especiais atrapalham o processamento da string de consulta no código atrás .

Aqui está o que eu fiz: Decode Token depois de codificá-lo para URL (em resumo)

Primeiro eu tive que codificar o usuário GenerateEmailConfirmationToken que foi gerado. (Standard acima do conselho)

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

e na ação “Confirmar” do seu controlador eu tive que decodificar o Token antes de validá-lo.

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

No meu caso, eu só preciso fazer HttpUtility.UrlEncode antes de enviar um email. Nenhum HttpUtility.UrlDecode durante a redefinição.

Aqui eu tenho o mesmo problema, mas depois de um lote de tempo eu fundei que no meu caso o erro de token inválido foi levantado pelo fato de que minha class de conta personalizada tem a propriedade Id redeclarada e substituída.

Curtiu isso:

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

Então, para consertá-lo, acabei de remover essa propriedade e gerar novamente o esquema do database apenas para ter certeza.

Remover isso resolve o problema.