it-swarm.com.de

Asp.NET Identity 2 gibt den Fehler "Ungültiges Token" aus

Ich verwende Asp.Net-Identity-2 und versuche, den E-Mail-Bestätigungscode mit der folgenden Methode zu verifizieren. Ich erhalte jedoch eine "Invalid Token" Fehlermeldung. 

  • Der Benutzermanager meiner Anwendung sieht folgendermaßen aus:

    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
    
  • Meine Aktion zum Generieren des Tokens ist (und selbst wenn ich das Token hier überprüfe, erhalte ich die Meldung "Ungültiges Token"):

    [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
    
  • Meine Aktion zum Überprüfen des Tokens ist (hier bekomme ich immer "Ungültiges Token", wenn ich das Ergebnis überprüfe):

    [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
    

Ich weiß nicht, was fehlen könnte oder was falsch ist ...

44
Julio Schurt

Weil Sie hier ein Token für das Zurücksetzen des Kennworts generieren:

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

Tatsächlich wird jedoch versucht, das Token für E-Mails zu überprüfen: 

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

Dies sind 2 verschiedene Token. 

In Ihrer Frage sagen Sie, dass Sie versuchen, die E-Mail zu bestätigen, aber Ihr Code ist für das Zurücksetzen des Kennworts bestimmt. Welches machst du?

Wenn Sie eine E-Mail-Bestätigung benötigen, generieren Sie das Token über

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

und bestätigen sie mit 

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

Wenn Sie das Kennwort zurücksetzen müssen, generieren Sie das Token folgendermaßen:

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

und bestätige es so:

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

Ich bin auf dieses Problem gestoßen und habe es gelöst. Es gibt mehrere mögliche Gründe.

1. Probleme mit der URL-Codierung (wenn das Problem "zufällig" auftritt)

Wenn dies zufällig geschieht, stoßen Sie möglicherweise auf Probleme mit der URL-Codierung. Aus unbekannten Gründen ist das Token nicht für URL-Safe ausgelegt, dh es enthält möglicherweise ungültige Zeichen, wenn es durch eine URL geleitet wird (z per E-Mail gesendet werden).

In diesem Fall sollten HttpUtility.UrlEncode(token) und HttpUtility.UrlDecode(token) verwendet werden. 

Wie Pereira in seinen Kommentaren sagte, ist UrlDecode nicht (oder manchmal nicht?) Erforderlich. Bitte beides versuchen. Vielen Dank.

2. Nicht übereinstimmende Methoden (E-Mail- und Kennwort-Token)

Zum Beispiel: 

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

und

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

Das vom E-Mail-Token bereitgestellte Token kann vom Reset-Kennwort-Token-Anbieter nicht bestätigt werden. 

Aber wir werden die Ursache dafür sehen.

3. Verschiedene Tokenanbieter

Auch wenn Sie verwenden: 

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

zusammen mit

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

der fehler könnte noch passieren.

Mein alter Code zeigt warum:

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

und:

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

Beachten Sie, dass in diesem Code jedes Mal, wenn eine UserManager erstellt wird (oder new_ed), auch eine neue dataProtectionProvider generiert wird. Wenn also ein Benutzer die E-Mail erhält und auf den Link klickt:

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

Die Variable AccountController ist nicht mehr die alte und der _userManager und sein Tokenanbieter sind nicht mehr vorhanden. Der neue Tokenanbieter schlägt daher fehl, weil er kein Token im Speicher hat.

Daher müssen wir eine einzige Instanz für den Tokenanbieter verwenden. Hier ist mein neuer Code und es funktioniert gut:

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

und:

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

Es kann keine elegante Lösung genannt werden, aber es traf die Wurzel und löste mein Problem.

65
cheny

Ich habe die Fehlermeldung "Ungültiges Token" erhalten, auch wenn der Code wie folgt lautet:

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

In meinem Fall stellte sich als Problem heraus, dass ich den Benutzer manuell erstellt und zur Datenbank hinzugefügt habe, ohne die UserManager.Create(...)-Methode zu verwenden. Der Benutzer war in der Datenbank vorhanden, jedoch ohne Sicherheitsstempel.

Es ist interessant, dass die Variable GenerateEmailConfirmationToken ein Token zurückgegeben hat, ohne sich über den fehlenden Sicherheitsstempel zu beklagen. Dieses Token konnte jedoch niemals validiert werden.

31
mendel

Abgesehen davon habe ich gesehen, dass der Code selbst nicht funktioniert, wenn er nicht codiert ist.

Ich habe vor kurzem mit der Kodierung meiner auf folgende Weise begonnen:

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

Und wenn ich bereit bin, es zurückzulesen:

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

Um ehrlich zu sein, bin ich überrascht, dass es überhaupt nicht richtig codiert wird. 

18
LTMOD

In meinem Fall konvertierte unsere AngularJS-App alle Pluszeichen (+) in leere Leerzeichen (""), sodass das Token tatsächlich ungültig war, als es zurückgegeben wurde. 

Um das Problem zu beheben, habe ich in unserer ResetPassword-Methode im AccountController vor dem Aktualisieren des Kennworts einfach ein Ersetzen hinzugefügt:

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

Ich hoffe, das hilft allen, die mit Identity in einer Web-API und AngularJS arbeiten.

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

                code = HttpUtility.UrlEncode(code);

// Rest E-Mail senden


decodieren Sie den Code nicht 

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

Folgendes habe ich getan: Token dekodieren, nachdem es für URL kodiert wurde (kurz) 

Zuerst musste ich den generierten User GenerateEmailConfirmationToken kodieren. (Standard über dem Ratschlag)

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

und in der "Confirm" Action Ihres Controllers musste ich das Token dekodieren, bevor ich es validierte.

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

Wir sind auf diese Situation mit einer Reihe von Benutzern gestoßen, bei denen alles gut funktioniert hat. Wir haben es vom Symantec-E-Mail-Schutzsystem isoliert, das Links in unseren E-Mails an Benutzer mit sicheren Links ersetzt, die zur Validierung auf ihre Website geleitet werden, und den Benutzer dann auf den ursprünglichen Link umleitet, den wir gesendet haben.

Das Problem ist, dass sie eine Dekodierung einführen ... Sie scheinen eine URL-Kodierung für den generierten Link einzugeben, um unseren Link als Abfrageparameter in ihre Website einzubetten. Wenn der Benutzer jedoch klickt, dekodiert clicksafe.symantec.com die URL decodiert den ersten Teil, den sie zum Codieren benötigten, aber auch den Inhalt unserer Abfragezeichenfolge. Anschließend wurde die URL, zu der der Browser umgeleitet wird, dekodiert. Wir befinden uns wieder in dem Zustand, in dem die Sonderzeichen die Abfragezeichenfolge im Code dahinter stören . 

1
user9296906

Stellen Sie sicher, dass Sie beim Generieren Folgendes verwenden: 

GeneratePasswordResetTokenAsync(user.Id)

Und bestätigen Sie, dass Sie verwenden:

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

Wenn Sie sicherstellen, dass Sie die übereinstimmenden Methoden verwenden, dies jedoch immer noch nicht funktioniert, stellen Sie sicher, dass user.Id in beiden Methoden identisch ist. (Manchmal ist Ihre Logik möglicherweise nicht korrekt, da Sie die Verwendung derselben E-Mail für die Registrierung usw. zulassen.)

1
Grey Wolf

Hier habe ich dasselbe Problem, aber nach einiger Zeit habe ich festgestellt, dass in meinem Fall der ungültige Tokenfehler durch die Tatsache ausgelöst wurde, dass meine benutzerdefinierte Account-Klasse die Id-Eigenschaft neu deklariert und überschrieben hat. 

So wie das: 

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

Um es zu beheben, habe ich diese Eigenschaft entfernt und das Datenbankschema erneut erstellt, nur um sicher zu gehen.

Das Entfernen dieses Problems löst das Problem.

1
Diego Garcia

Stellen Sie sicher, dass das von Ihnen generierte Token nicht schnell abläuft. Ich habe es zum Testen auf 10 Sekunden geändert und der Fehler wird immer zurückgegeben.

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

Vielleicht ist dies ein alter Thread, aber für den Fall habe ich mir mit dem zufälligen Auftreten dieses Fehlers den Kopf gekratzt. Ich habe alle Threads bezüglich jedes Vorschlags überprüft und jeden Vorschlag überprüft, aber - wie es scheint - einige der Codes wurden als "ungültiges Token" zurückgegeben. Nach einigen Abfragen an die Benutzerdatenbank habe ich schließlich festgestellt, dass diese "ungültigen Token" Fehler, die direkt mit Leerzeichen oder anderen nicht alphanumerischen Zeichen in Benutzernamen zusammenhängen. Die Lösung war dann leicht zu finden. Konfigurieren Sie einfach den UserManager so, dass diese Zeichen in den Benutzernamen zulässig sind .. Dies kann direkt nach dem Erstellen des Ereignisses durch den User Manager erfolgen. Fügen Sie eine neue UserValidator-Einstellung hinzu, um die entsprechende Eigenschaft auf diese Weise auf false zu setzen:

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

Ich hoffe, das könnte "späten Ankömmlingen" wie mir helfen!

1
Josep Alacid

Mein Problem war, dass mir ein <input asp-for="Input.Code" type="hidden" />-Steuerelement in meinem Formular zum Zurücksetzen des Passworts fehlte

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

Mein Problem war, dass in der E-Mail, die das ConfirmationToken enthielt, ein Tippfehler vorlag:

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

Dies bedeutete, dass das zusätzliche Apostroph an das Ende des ConfirmationToken angehängt wurde.

D'oh!

0
Nacht

tl; dr: Registrieren Sie einen benutzerdefinierten Token-Anbieter in aspnet core 2.2 , um ihn zu verwenden AES-Verschlüsselung anstelle von MachineKey-Schutz, Gist: https://Gist.github.com/cyptus/dd9b2f90c190aaed4e807177c45c3c8b

ich bin mit aspnet core 2.2 auf dasselbe Problem gestoßen, da Cheny darauf hingewiesen hat, dass die Instanzen des Token-Anbieters dieselben sein müssen. das klappt bei mir da nicht

  • ich habe different API-projects, das das Token generiert und das Token zum Zurücksetzen des Passworts erhält
  • die APIs können auf different instances von virtuellen Maschinen ausgeführt werden, sodass der Maschinenschlüssel nicht identisch ist
  • die API kann restart und das Token wäre ungültig, da es nicht mehr same instance ist

ich könnte services.AddDataProtection().PersistKeysToFileSystem(new DirectoryInfo("path")) verwenden, um das Token im Dateisystem zu speichern und Neustart und Probleme mit der gemeinsamen Nutzung mehrerer Instanzen zu vermeiden, aber das Problem mit mehreren Projekten nicht umgehen, da jedes Projekt eine eigene Datei generiert.

die Lösung für mich besteht darin, die MachineKey-Datenschutzlogik durch eine eigene Logik zu ersetzen, die AES then HMAC verwendet, um das Token symmetrisch mit einem Schlüssel aus meinen eigenen Einstellungen zu verschlüsseln, den ich für Maschinen, Instanzen und Projekte freigeben kann. Ich habe die Verschlüsselungslogik von Ver- und Entschlüsseln eines Strings in C #? (Inhalt: https://Gist.github.com/jbtule/4336842#file-aesthenhmac-cs übernommen ) und implementierte einen benutzerdefinierten 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();
        }
    }

und den SettingsSupplier, den ich in meinem Projekt verwende, um meine Einstellungen bereitzustellen

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

endlich den Provider im Startup registrieren:

 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
0
cyptus

In meinem Fall muss ich nur HttpUtility.UrlEncode ausführen, bevor ich eine E-Mail senden kann. Kein HttpUtility.UrlDecode während des Zurücksetzens.

0
sailen