Asp.NET Identity 2 给出“无效令牌”错误
Posted
技术标签:
【中文标题】Asp.NET Identity 2 给出“无效令牌”错误【英文标题】:Asp.NET Identity 2 giving "Invalid Token" error 【发布时间】:2014-10-13 20:17:54 【问题描述】:我正在使用 Asp.Net-Identity-2 并尝试使用以下方法验证电子邮件验证码。但我收到 "Invalid Token" 错误消息。
我的应用程序的用户管理器是这样的:
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
我不知道可能缺少什么或出了什么问题......
【问题讨论】:
【参考方案1】:我遇到了这个问题并解决了。有几个可能的原因。
1。 URL 编码问题(如果问题“随机”发生)
如果这种情况随机发生,您可能会遇到 url 编码问题。 由于未知原因,该令牌不是为 url 安全而设计的,这意味着它在通过 url 传递时可能包含无效字符(例如,如果通过电子邮件发送)。
在这种情况下,应使用HttpUtility.UrlEncode(token)
和HttpUtility.UrlDecode(token)
。
正如 oão Pereira 在他的 cmets 中所说,UrlDecode
不是(或有时不是?)必需的。请尝试两者。谢谢。
2。不匹配的方法(电子邮件与密码令牌)
例如:
var code = await userManager.GenerateEmailConfirmationTokenAsync(user.Id);
和
var result = await userManager.ResetPasswordAsync(user.Id, code, newPassword);
email-token-provide 生成的令牌无法被 reset-password-token-provider 确认。
但我们会看到发生这种情况的根本原因。
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
及其令牌提供程序也不再是旧的。因此,新的令牌提供程序将失败,因为它的内存中没有该令牌。
因此,我们需要为令牌提供者使用单个实例。这是我的新代码,它工作正常:
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;
它不能称为优雅的解决方案,但它击中了根源并解决了我的问题。
【讨论】:
对这个很棒的答案只有一个注释! :) 令牌必须是UrlEncoded
,但它不应该是 UrlDecoded
,至少在 MVC 中作为方法参数接收时,因为它是自动解码的。如果我们再次对其进行解码,我们会使令牌无效,因为 +
字符被替换为空格。
这解决了我的问题,因为我需要使用令牌across projects, instances and computers
。我为此实现了一个自定义AES encryption
,详情请参阅我的答案:***.com/a/56355282/1216595
使用解决方案 #3 解决了 Asp Net Core 3.1 应用程序中的问题
我再次点击了这个答案。上次我使用 UserManager 的单个实例来解决它,该实例注册了使用 UserManager 作为单例的服务。相反,在这个其他项目中,如果我做同样的事情,它会抛出一个异常,说我不能将该服务注册为单例,因为 UserManager 需要一个瞬态范围。您上面的解决方案无法编译(我可以报告很多问题)。那么什么可能是有效的解决方法呢?问题显然是#3(令牌提供者的不同实例)
我又修好了。该问题是由 User 表中缺少 SecurityStamp 列引起的。我已将其删除,但没有该列将无法正常工作【参考方案2】:
因为您在这里生成密码重置令牌:
string code = UserManager.GeneratePasswordResetToken(user.Id);
但实际上试图验证电子邮件的令牌:
result = await UserManager.ConfirmEmailAsync(id, code);
这是 2 个不同的令牌。
在您的问题中,您说您正在尝试验证电子邮件,但您的代码用于密码重置。你在做什么?
如果您需要电子邮件确认,则通过生成令牌
var emailConfirmationCode = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
并通过
确认var confirmResult = await UserManager.ConfirmEmailAsync(userId, code);
如果您需要重置密码,请生成这样的令牌:
var code = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
并像这样确认:
var resetResult = await userManager.ResetPasswordAsync(user.Id, code, newPassword);
【讨论】:
如何使 ConfirmEmailAsync 返回 failed 而不是成功,如果该令牌已被使用一次。喜欢用户尝试从他/她的电子邮件地址重新访问链接? @user2904995 使令牌无效,您需要更改SecurityStamp
字段。这将使所有以前有效的令牌失效,包括过去使用过的令牌。【参考方案3】:
即使使用如下代码,我也收到“无效令牌”错误:
var emailCode = UserManager.GenerateEmailConfirmationToken(id);
var result = UserManager.ConfirmEmail(id, emailCode);
在我的情况下,问题原来是我是手动创建用户并将他添加到数据库中而不使用UserManager.Create(...)
方法。用户存在于数据库中,但没有安全标记。
有趣的是,GenerateEmailConfirmationToken
返回了一个令牌而没有抱怨缺少安全标记,但该令牌永远无法验证。
【讨论】:
在我的情况下,用户是从旧数据库迁移的,因此安全标记为空,我运行它来修复它:UPDATE AspNetUsers SET SecurityStamp = NewID()
我建议使用 UPDATE AspNetUsers SET SecurityStamp = NewID() WHERE SecurityStamp is null
。就我而言,某些用户的 SecurityStamp 很好,我不想惹他们。
要记住的一点是,留给自己的设备的 Identity 生成小写的 guid,而 NewID() 返回一个大写的 guid(至少在 SSMS 中)。考虑使用 LOWER(NewID())
对我来说实际上是在检查令牌。我通过我的 repo 而不是 UserManager 拉用户,因此我的 repo 用户称为 ResetPasswordAsync。基本相同的问题【参考方案4】:
除此之外,如果没有编码,我还看到代码本身会失败。
我最近开始用以下方式编码我的:
string code = manager.GeneratePasswordResetToken(user.Id);
code = HttpUtility.UrlEncode(code);
然后当我准备回读时:
string code = IdentityHelper.GetCodeFromRequest(Request);
code = HttpUtility.UrlDecode(code);
说实话,我很惊讶它一开始就没有被正确编码。
【讨论】:
仅在作为reset链接的查询字符串值时才需要编码。如果您在应用程序内部提供密码重置表单,其中代码作为隐藏值或类似的东西传递,则可以在不编码的情况下使用它。 嘿,先生。感谢您的光临!我在 ASP.NET Core 5.0 中通过以下方式做到了:Encoding.UTF8.GetString(Convert.FromBase64String(code));【参考方案5】:在我的例子中,我们的 AngularJS 应用程序将所有加号 (+) 转换为空格 (" "),因此令牌在传回时确实无效。
为了解决这个问题,在 AccountController 的 ResetPassword 方法中,我只是在更新密码之前添加了一个替换:
code = code.Replace(" ", "+");
IdentityResult result = await AppUserManager.ResetPasswordAsync(user.Id, code, newPassword);
我希望这可以帮助其他在 Web API 和 AngularJS 中使用 Identity 的人。
【讨论】:
对于更正式的方法,我建议var callbackUrl = new Uri(Request.RequestUri, RequestContext.VirtualPathRoot).AbsoluteUri + $"#/resetPassword?username=WebUtility.UrlEncode(user.UserName)&code=WebUtility.UrlEncode(code)";
将用户名和代码正确 url 编码到客户端页面(例如 Angular),以让用户设置密码并完成请求
默认token是base64编码的,不是URL安全的,需要进行URL编码。您可以覆盖或包装令牌提供程序,并返回 base64url 编码的令牌,避免像您已经做过的特殊字符。【参考方案6】:
tl;dr: 在 aspnet core 2.2 中注册自定义令牌提供程序以使用 AES 加密而不是 MachineKey 保护,要点:https://gist.github.com/cyptus/dd9b2f90c190aaed4e807177c45c3c8b
我遇到了与aspnet core 2.2
相同的问题,因为 cheny 指出令牌提供程序的实例需要相同。
这对我不起作用,因为
different API-projects
,它确实生成了令牌和
收到重置密码的令牌
API 可能在different instances
的虚拟机上运行,因此机器密钥不会是
一样
API 可能restart
并且令牌将无效,因为它是
不再是same instance
我可以使用
services.AddDataProtection().PersistKeysToFileSystem(new DirectoryInfo("path"))
将令牌保存到文件系统并避免重启和多个实例共享问题,但无法解决多个项目的问题,因为每个项目都会生成一个自己的文件。
我的解决方案是用自己的逻辑替换 MachineKey 数据保护逻辑,该逻辑确实使用 AES then HMAC
用我自己的设置中的密钥对令牌进行对称加密,我可以在机器、实例和项目之间共享。我从
Encrypt and decrypt a string in C#?
(要点: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();
以及我在项目中使用的 SettingsSupplier 来提供我的设置
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);
最后在 Startup 中注册提供者:
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
【讨论】:
【参考方案7】:string code = _userManager.GeneratePasswordResetToken(user.Id);
code = HttpUtility.UrlEncode(code);
//发送休息邮件
不要解码代码
var result = await _userManager.ResetPasswordAsync(user.Id, model.Code, model.Password);
【讨论】:
您对 not 解码代码的评论对我不起作用。只有解码代码才会成功。 @AaronHudon 可能取决于您是通过 url 字符串还是在请求正文(帖子)中发送它。 这似乎取决于您使用的是 WebAPI 还是 MVC 控制器。 MVC控制器URL上的模型Binder默认解码!【参考方案8】:这是我所做的:在为 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);
【讨论】:
【参考方案9】:在这里我遇到了同样的问题,但经过很长时间后,我发现在我的情况下,无效令牌错误是由于我的自定义 Account 类重新声明和覆盖了 Id 属性而引发的。
像这样:
public class Account : IdentityUser
[ScaffoldColumn(false)]
public override string Id get; set;
//Other properties ....
为了解决这个问题,我刚刚删除了该属性并再次生成了数据库模式以确保安全。
删除它可以解决问题。
【讨论】:
【参考方案10】:用 asp.net core 解决这个问题,经过大量挖掘后,我意识到我已经在 Startup 中打开了这个选项:
services.Configure<RouteOptions>(options =>
options.LowercaseQueryStrings = true;
);
这当然会使查询字符串中的令牌无效。
【讨论】:
完全正确!!!你拯救了我的一天,谢谢。【参考方案11】:以下解决方案在 WebApi 中帮助了我:
注册
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
EmailService emailService = new EmailService();
var url = _configuration["ServiceName"];
var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var encodedToken = HttpUtility.UrlEncode(token);
// .Net Core 2.1, Url.Action return null
// Url.Action("confirm", "account", new userId = user.Id, code = token , protocol: HttpContext.Request.Scheme);
var callbackUrl = _configuration["ServiceAddress"] + $"/account/confirm?userId=user.Id&code=encodedToken";
var message = emailService.GetRegisterMailTemplate(callbackUrl, url);
await emailService.SendEmailAsync( model.Email, $"please confirm your registration url", message );
确认
[Route("account/confirm")]
[AllowAnonymous]
[HttpGet]
public async Task<IActionResult> ConfirmEmail(string userId, string code)
if (userId == null || code == null)
return Content(JsonConvert.SerializeObject( new result = "false", message = "data is incorrect" ), "application/json");
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
return Content(JsonConvert.SerializeObject(new result = "false", message = "user not found" ), "application/json");
//var decodedCode = HttpUtility.UrlDecode(code);
//var result = await _userManager.ConfirmEmailAsync(user, decodedCode);
var result = await _userManager.ConfirmEmailAsync(user, code);
if (result.Succeeded)
return Content(JsonConvert.SerializeObject(new result = "true", message = "ок", token = code ), "application/json");
else
return Content(JsonConvert.SerializeObject(new result = "false", message = "confirm error" ), "application/json");
【讨论】:
奇妙的var encodedToken = HttpUtility.UrlEncode(token);
做了魔法【参考方案12】:
受@cheny 发布的解决方案#3 的启发,我意识到如果您使用相同的UserManager
实例,则生成的代码将被接受。但在实际场景中,验证代码发生在用户单击电子邮件链接后的第二次 API 调用中。
这意味着创建了UserManager
的新实例,并且无法验证第一次调用的第一个实例生成的代码。使其工作的唯一方法是确保在数据库用户表中有SecurityStamp
列。
将使用 UserManager
的类注册为单例会在应用程序启动时引发异常,因为 UserManager
类会自动注册为 Scoped
生命周期
【讨论】:
谢谢,我在用户表中填写了 SecurityStamp 列,它解决了我的问题。【参考方案13】:确保在生成时使用:
GeneratePasswordResetTokenAsync(user.Id)
并确认您使用:
ResetPasswordAsync(user.Id, model.Code, model.Password)
如果您确定使用了匹配方法,但仍然无法正常工作,请验证user.Id
在两种方法中是否相同。 (有时您的逻辑可能不正确,因为您允许使用相同的电子邮件进行注册表等)
【讨论】:
【参考方案14】:也许这是一个旧线程,但就这种情况而言,我一直在为这个错误的随机发生而摸不着头脑。我一直在检查所有线程并验证每个建议,但 - 随机似乎 - 一些返回为“无效令牌”的代码。 在对用户数据库进行一些查询后,我终于发现那些“无效令牌”错误与用户名中的空格或其他非字母数字字符直接相关。 那时很容易找到解决方案。只需配置 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;
希望这可以帮助像我这样的“迟到者”!
【讨论】:
关于编码/解码以避免空格和其他符号干扰,我正在使用这个很有魅力的建议:***.com/questions/27535233/…【参考方案15】:确保您生成的令牌不会很快过期 - 我已将其更改为 10 秒进行测试,它总是会返回错误。
if (dataProtectionProvider != null)
manager.UserTokenProvider =
new DataProtectorTokenProvider<AppUser>
(dataProtectionProvider.Create("ConfirmationToken"))
TokenLifespan = TimeSpan.FromHours(3)
//TokenLifespan = TimeSpan.FromSeconds(10);
;
【讨论】:
【参考方案16】:我们在一组用户中遇到了这种情况,并且一切正常。我们已将其隔离到赛门铁克的电子邮件保护系统,该系统将我们电子邮件中的链接替换为安全链接,这些链接会转到他们的网站进行验证,然后将用户重定向到我们发送的原始链接。
问题是他们正在引入解码...他们似乎在生成的链接上执行 URL 编码,以将我们的链接作为查询参数嵌入到他们的站点,但是当用户单击并 clicksafe.symantec.com 解码时它解码了他们需要编码的第一部分的url,还有我们的查询字符串的内容,然后浏览器被重定向到的URL已经被解码,我们又回到了特殊字符弄乱查询字符串处理的状态后面的代码。
【讨论】:
【参考方案17】:就我而言,我只需要在发送电子邮件之前执行 HttpUtility.UrlEncode。重置期间没有 HttpUtility.UrlDecode。
【讨论】:
【参考方案18】:与chenny的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)
;
【讨论】:
【参考方案19】:如果有人遇到这种情况,事实证明该令牌不是 URL 友好的,因此我不得不将它包装在 HttpUtility.UrlEncode() 中,如下所示:
var callback = Url.Content($"this.Request.Scheme://this.Request.Hostthis.Request.PathBase/reset-password?token=HttpUtility.UrlEncode(token)&email=user.Email");
【讨论】:
【参考方案20】:我已经解决了大部分描述的提示“无效令牌”问题。这是我的 blazor 项目解决方案。核心在StringExtensions
类中。
当用户注册他/她的电子邮件时生成电子邮件:
user = new IdentityUser UserName = email, Email = email ;
var createUser = await _userManager.CreateAsync(user, password);
if (createUser.Succeeded)
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var baseUri = NavMgr.BaseUri;
var setNewPasswordUri = baseUri + "confirm-password";
var urlWithParams = StringExtensions.GenerateUrl(token, emailTo, url);
await SendAsync( urlWithParams ); // use your own Email solution send the email
电子邮件确认(用户点击邮件中的链接)
@page "/confirm-email"
<h3>Confirm email</h3>
@Error
[Inject]
UserManager<IdentityUser> UserMgr get; set;
[Inject]
NavigationManager NavMgr get; set;
protected override Task OnInitializedAsync()
var url = NavMgr.Uri;
Token = StringExtensions.GetParamFromUrl(url, "token");
Email = StringExtensions.GetParamFromUrl(url, "email");
log.Trace($"Initialised with email=Email , token=Token");
return ActivateEmailAsync();
private async Task ActivateEmailAsync()
isProcessing = true;
Error = null;
log.Trace($"ActivateEmailAsync started for Email");
isProcessing = true;
Error = null;
try
var user = await UserMgr.FindByEmailAsync(Email);
if (user != null)
if (!string.IsNullOrEmpty(Token))
var result = await UserMgr.ConfirmEmailAsync(user, Token);
if (result.Succeeded)
// Show user , that account is activated
else
foreach (var error in result.Errors)
Error += error.Description;
log.Error($"Setting new password failed for Email due to the: Error");
else
log.Error("This should not happen. Token is null or empty");
catch (Exception exc)
Error = $"Activation failed";
isProcessing = false;
public static class StringExtensions
/// <summary>
/// Encode string to be safe to use it in the URL param
/// </summary>
/// <param name="toBeEncoded"></param>
/// <returns></returns>
public static string Encode(string toBeEncoded)
var result = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(toBeEncoded));
return result;
/// <summary>
/// Decode from the url safe string the original value
/// </summary>
/// <param name="toBeDecoded"></param>
/// <returns></returns>
public static string Decode(string toBeDecoded)
var decodedBytes = WebEncoders.Base64UrlDecode(toBeDecoded);
var result = Encoding.UTF8.GetString(decodedBytes);
return result;
public static string GenerateUrl(string token, string emailTo, string baseUri, string tokenParamName = "token", string emailParamName = "email")
var tokenEncoded = StringExtensions.Encode(token);
var emailEncoded = StringExtensions.Encode(emailTo);
var queryParams = new Dictionary<string, string>();
queryParams.Add(tokenParamName, tokenEncoded);
queryParams.Add(emailParamName, emailEncoded);
var urlWithParams = QueryHelpers.AddQueryString(baseUri, queryParams);
return urlWithParams;
public static string GetParamFromUrl(string uriWithParams, string paramName)
var uri = new Uri(uriWithParams, UriKind.Absolute);
var result = string.Empty;
if (QueryHelpers.ParseQuery(uri.Query).TryGetValue(paramName, out var paramToken))
var queryToken = paramToken.First();
result = StringExtensions.Decode(queryToken);
return result;
【讨论】:
【参考方案21】:我在重置密码场景中遇到了无效令牌。根本原因是,我正在为不正确的IndentityUser
生成重置令牌。在简化的代码中可以很容易地发现它,但是在更复杂的代码中我花了一些时间来修复它。
我应该使用代码:
var user = await UserMgr.FindByEmailAsync(Model.Email);
string resetToken = await _userManager.GeneratePasswordResetTokenAsync(user);
但我错了(创建另一个IndentityUser
)。
// This is example "How it should not be done"
var user = await UserMgr.FindByEmailAsync(Model.Email);
user = new IdentityUser UserName = email, Email = email ; // This must not be her !!!! We need to use user found by UserMgr.FindByEmailAsync(Model.Email);
string resetToken = await _userManager.GeneratePasswordResetTokenAsync(user);
完整的简化代码在这里:
private async Task GenerateResetToken()
var user = await UserMgr.FindByEmailAsync(Model.Email);
if (user == null)
Model.Error = "Not registered";
else
try
var _userManager = SignInMgr.UserManager;
UserMgr.FindByEmailAsync(Model.Email);
string resetToken = await _userManager.GeneratePasswordResetTokenAsync(user);
if (resetToken == null)
log.Error("Cannot get token from GeneratePasswordResetTokenAsync");
else
// Reset token generated. Send email to user
catch (Exception exc)
log.Error(exc, $"Password reset failed due to the exc.Message");
【讨论】:
【参考方案22】:我的问题是包含 ConfirmationToken 的电子邮件中有错字:
<p>Please confirm your account by <a href=@ViewBag.CallbackUrl'>clicking here</a>.</p>
这意味着额外的撇号被附加到 ConfirmationToken 的末尾。
天啊!
【讨论】:
【参考方案23】:我的问题是我的重置密码表单中缺少<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" />
【讨论】:
以上是关于Asp.NET Identity 2 给出“无效令牌”错误的主要内容,如果未能解决你的问题,请参考以下文章
Asp.net Core 2.0 Identity.TwoFactorRememberMe 到期
Asp.Net Core 2.1 Identity - UserStore 依赖注入
Microsoft 'Identity Platform' 是不是(轻松)与'(ASP.NET) Identity Framework' - MVC5 & .NET 4.7.2 集成?