Asp.NET Identity 2 con errore “Token non valido”

Sto utilizzando Asp.Net-Identity-2 e sto provando a verificare il codice di verifica dell’email utilizzando il metodo seguente. Ma ricevo un messaggio di errore “Token non valido” .

  • Il Gestore utenti della mia applicazione è come questo:

    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 
  • La mia azione per generare il token è (e anche se controllo il token qui, ricevo il messaggio “Token non valido”):

     [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 
  • La mia azione per controllare il token è (qui, ottengo sempre “Token non valido” quando controllo il risultato):

     [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

Non so cosa potrebbe mancare o cosa non va …

Perché stai generando un token per reimpostare la password qui:

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

Ma in realtà provando a convalidare il token per la posta elettronica:

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

Questi sono 2 diversi token.

Nella tua domanda dici che stai provando a verificare l’email, ma il tuo codice è per la reimpostazione della password. Quale stai facendo?

Se hai bisogno di una conferma via email, genera un token tramite

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

e confermare tramite

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

Se hai bisogno di reimpostare la password, genera un token come questo:

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

e confermalo in questo modo:

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

Ho riscontrato questo problema e l’ho risolto. Ci sono diverse possibili ragioni.

1. Problemi di codifica dell’URL (se il problema si verifica “in modo casuale”)

Se ciò accade casualmente, potresti avere problemi di codifica dell’URL. Per motivi sconosciuti, il token non è progettato per url-safe, il che significa che potrebbe contenere caratteri non validi quando viene passato attraverso un url (ad esempio, se inviato tramite e-mail).

In questo caso, è necessario utilizzare HttpUtility.UrlEncode(token) e HttpUtility.UrlDecode(token) .

Come ha detto Pereira nei suoi commenti, UrlDecode non è (o qualche volta no?) UrlDecode . Prova entrambi per favore. Grazie.

2. Metodi non corrispondenti (token e-mail e password)

Per esempio:

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

e

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

Il token generato dal token-e-mail fornito non può essere confermato dal reset-password-token-provider.

Ma vedremo la causa principale del perché questo accade.

3. Diverse istanze di provider di token

Anche se stai usando:

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

insieme a

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

l’errore potrebbe ancora accadere.

Il mio vecchio codice mostra perché:

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

Fai attenzione che in questo codice, ogni volta che viene creato un UserManager (o new ), viene generato anche un nuovo dataProtectionProvider . Quindi, quando un utente riceve l’e-mail e fa clic sul 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"); } 

AccountController non è più il vecchio e nessuno dei due è il _userManager e il relativo provider di token. Quindi il nuovo provider di token fallirà perché non ha quel token nella sua memoria.

Quindi dobbiamo usare una singola istanza per il provider di token. Ecco il mio nuovo codice e funziona bene:

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

Non poteva essere definito una soluzione elegante, ma ha colpito la radice e risolto il mio problema.

Stavo ottenendo l’errore “Token non valido” anche con codice come questo:

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

Nel mio caso il problema è stato che stavo creando l’utente manualmente e aggiungendolo al database senza utilizzare il UserManager.Create(...) . L’utente esisteva nel database ma senza un timbro di sicurezza.

È interessante notare che GenerateEmailConfirmationToken restituito un token senza lamentarsi della mancanza di timestamp, ma che il token non può mai essere convalidato.

Oltre a questo, ho visto il codice stesso fallire se non è codificato.

Di recente ho iniziato a codificare il mio nel seguente modo:

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

E poi quando sono pronto a rileggerlo:

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

Per essere onesti, sono sorpreso che non sia codificato correttamente in primo luogo.

Nel mio caso, la nostra app AngularJS ha convertito tutti i segni più (+) in spazi vuoti (“”) in modo che il token fosse effettivamente non valido quando è stato restituito.

Per risolvere il problema, nel nostro metodo ResetPassword in AccountController, ho semplicemente aggiunto una sostituzione prima di aggiornare la password:

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

Spero che questo aiuti chiunque altro a lavorare con Identity in un’API Web e AngularJS.

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

// invia email di resto


non decodificare il codice

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

Forse questo è un thread vecchio ma, solo per il caso, mi sono grattato la testa con l’occorrenza casuale di questo errore. Ho controllato tutti i thread e verificato ogni suggerimento ma, in modo casuale, alcuni dei codici sono stati restituiti come “token non valido”. Dopo alcune query al database degli utenti, ho finalmente riscontrato che quegli errori “token non validi” erano direttamente correlati con spazi o altri caratteri non alfanumerici nei nomi utente. La soluzione è stata facile da trovare allora. Basta configurare UserManager per consentire quei caratteri nei nomi degli utenti. Questo può essere fatto subito dopo la creazione dell’evento da parte del gestore utenti, aggiungendo una nuova impostazione UserValidator per falsificare la proprietà corrispondente in questo modo:

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

Spero che questo possa aiutare gli “arrivi in ​​ritardo” come me!

Assicurati che quando generi, usi:

 GeneratePasswordResetTokenAsync(user.Id) 

E conferma di usare:

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

Se ti assicuri che stai utilizzando i metodi di corrispondenza, ma ancora non funziona, verifica che user.Id sia lo stesso in entrambi i metodi. (A volte la tua logica potrebbe non essere corretta perché permetti di usare la stessa email per il registro, ecc.)

Assicurati che il token generato non scada rapidamente: l’ho modificato in 10 secondi per il test e restituirebbe sempre l’errore.

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

Abbiamo incontrato questa situazione con un gruppo di utenti in cui tutto funzionava perfettamente. L’abbiamo isolato fino al sistema di protezione della posta elettronica di Symantec che sostituisce i link nelle nostre e-mail agli utenti con link sicuri che vanno al loro sito per la convalida e quindi reindirizza l’utente al link originale che abbiamo inviato.

Il problema è che stanno introducendo una decodifica … sembrano fare una codifica URL sul link generato per incorporare il nostro link come parametro di query sul loro sito ma poi quando l’utente fa clic e clickafe.symantec.com decodifica l’URL decodifica la prima parte di cui hanno bisogno per codificare, ma anche il contenuto della nostra stringa di query e quindi l’URL a cui il browser viene reindirizzato è stato decodificato e torniamo nello stato in cui i caratteri speciali rovinano la gestione della stringa di query nel codice sottostante .

Ecco cosa ho fatto: Decode Token dopo averlo codificato per URL (in breve)

Per prima cosa ho dovuto codificare l’utente GenerateEmailConfirmationToken che è stato generato. (Standard sopra consiglio)

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

e nell’azione “Conferma” del tuo controller ho dovuto decodificare il token prima di convalidarlo.

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

Nel mio caso, ho solo bisogno di fare HttpUtility.UrlEncode prima di inviare una e-mail. No HttpUtility.UrlDecode durante il ripristino.

Qui ho lo stesso problema ma dopo un lasso di tempo ho fondato che nel mio caso l’errore token non valido è stato sollevato dal fatto che la mia class Account personalizzata ha la proprietà Id redeclared e overrided.

Come quello:

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

Quindi per risolverlo ho appena rimosso quella proprietà e generato di nuovo lo schema del database solo per essere sicuro.

La rimozione di questo risolve il problema.