ASP.NET 确认电子邮件中的 Core Identity 令牌无效
ASP.NET Core Identity invalid token on confirmation email
这是一个与此非常相似的问题
aspnet identity invalid token on confirmation email
但解决方案无效,因为我使用的是新的 ASP.NET Core 1.0,其中包括 ASP.NET Core Identity。
我的场景如下:
在后端(ASP.NET 核心)我有一个功能,可以发送带有 link 的密码重置电子邮件。为了生成 link 我必须使用 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);
}
这将生成一封带有 link 的电子邮件至:
http://myapp:8080/passwordreset?code=CfDJ8JBnWaVj6h1PtqlmlJaH57r9TRA5j7Ij1BVyeBUpqX+5Cq1msu9zgkuI32Iz9x/5uE1B9fKFp4tZFFy6lBTseDFTHSJxwtGu+jHX5cajptUBiVqIChiwoTODh7ei4+MOkX7rdNVBMhG4jOZWqqtZ5J30gXr/JmltbYxqOp4JLs8V05BeKDbbVO/Fsq5+jebokKkR5HEJU+mQ5MLvNURsJKRBbI3qIllj1RByXt9mufGRE3wmQf2fgKBkAL6VsNgB8w==
然后我的 AngularJs 应用程序将呈现一个带有表单的视图以输入和确认新密码,然后将一个 JSON 对象与新密码和从 URL.
中的查询参数中获取的代码
最后我的后端会收到 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();
}
问题是 Identity 响应
Invalid token
错误。我发现问题是代码不匹配,上面的代码将在 PUT 请求的 JSON 对象中收到,如下所示:
CfDJ8JBnWaVj6h1PtqlmlJaH57r9TRA5j7Ij1BVyeBUpqX 5Cq1msu9zgkuI32Iz9x/5uE1B9fKFp4tZFFy6lBTseDFTHSJxwtGu jHX5cajptUBiVqIChiwoTODh7ei4 MOkX7rdNVBMhG4jOZWqqtZ5J30gXr/JmltbYxqOp4JLs8V05BeKDbbVO/Fsq5 jebokKkR5HEJU mQ5MLvNURsJKRBbI3qIllj1RByXt9mufGRE3wmQf2fgKBkAL6VsNgB8w==
请注意,以前有 +
符号的地方现在有空格
符号,显然这会导致 Identity 认为标记不同。
出于某种原因 Angular 正在以不同的编码方式解码 URL 查询参数。
如何解决?
这个答案 为我指明了正确的方向。但正如我所说,它适用于不同的版本,现在它的解决方案略有不同。
答案还是一样:用 base 64 url 编码 token,然后用 base 64 url 解码它。这样 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);
}
我有一个类似的问题,我正在编码我的令牌,但它一直在验证失败,结果问题是这样的:options.LowercaseQueryStrings = true;
不要在 options.LowercaseQueryStrings
上设置 true
这会改变验证令牌的完整性,您将收到无效令牌错误。
// This allows routes to be in lowercase
services.AddRouting(options =>
{
options.LowercaseUrls = true;
options.LowercaseQueryStrings = false;
});
在我的 Asp.Net Core 3.0 项目中构建 ConfirmEmail 页面后,我 运行 遇到了同样的问题。
从 ConfirmEmail.cshtml.cs
中的 OnGetAsync
方法中删除以下行修复了问题:
code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
在脚手架登录页面中,code
被添加到 callbackUrl
,然后 URL 使用 HtmlEncoder.Default.Encode(callbackUrl)
编码。单击 link 时,解码会自动完成,code
就像确认电子邮件一样。
更新:
我注意到在 忘记密码 过程中 code
在被放入 callbackUrl
之前进行了 Base64 编码,这意味着 Base64 解码 是必要的。
因此,更好的解决方案是在将代码添加到 callbackUrl 之前将以下行添加到生成代码的任何位置。
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
这里是 link 到 issue 已经修复。
我已经尝试了上面的答案,但是这个 guide 对我有帮助。基本上,您需要对代码进行编码,否则,您会遇到一些奇怪的错误。总而言之,您需要这样做:
string code = HttpUtility.UrlEncode(UserManager.GenerateEmailConfirmationToken(userID));
在此之后,如果适用于您,解码代码:
string decoded = HttpUtility.UrlDecode(code)
(根据这个post:)
对于 resetPasswordAsync (identity manager)“token invalid”问题...因为“+”变成了space在 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>.";
现在您可以点击您的 link,然后您将使用“+”(按 %2B 编码)转到好的 url...您的令牌不会无效。 ..
ASP Core 2.1 也有类似的问题,让我摸不着头脑,因为 code
(token
) 的 encoding/decoding 对我不起作用。而且我总是遇到 userManager.ConfirmEmailAsync(user, code)
的 Invalid token 错误
解决方案:
事实证明,问题是用户不是使用 UserManager
创建的,而是使用 dbcontext 像 _dbContext.Users.AddAsync
在用 _userManager.CreateAsync
替换此创建方法后一切正常即使 code
(token
).
没有任何 encoding/decoding,对我来说也很好
在我的例子中,这个问题是由于 RegisterModel 中的 OnPostAsync 方法编码回调 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 '&' 变成 '&',从而使整个 link.
你也可以在验证reset put中的令牌时使用正则表达式。
var decode = token.Replace(" ", "+");
await _userManager.ResetPasswordAsync(user, decode, Password);
我在 GCP 中使用 Cloud 运行 托管我的网站时遇到了同样的问题,这里的 none 解决方案对我有用。
在我的例子中,问题出在实例本地存储的数据保护密钥上。日志中的以下条目暗示了问题:
Storing keys in a directory '/home/.aspnet/DataProtection-Keys' that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed.
因此,令牌仅对颁发它们的实例有效,当用户单击发送到他们电子邮件的 link 时,该实例已经被销毁。
解决方案是为密钥使用分布式存储,例如,通过 EntityFramework:
的数据库
using Microsoft.AspNetCore.DataProtection;
...
public void ConfigureServices(IServiceCollection services)
{
services.AddDataProtection()
.PersistKeysToDbContext<DbContext>();
services.AddIdentity<User, IdentityRole>()
.AddEntityFrameworkStores<AnotherDbContext>()
.AddDefaultTokenProviders();
}
可以找到不同类型存储的详细信息here。
这是一个与此非常相似的问题 aspnet identity invalid token on confirmation email 但解决方案无效,因为我使用的是新的 ASP.NET Core 1.0,其中包括 ASP.NET Core Identity。
我的场景如下:
在后端(ASP.NET 核心)我有一个功能,可以发送带有 link 的密码重置电子邮件。为了生成 link 我必须使用 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); }
这将生成一封带有 link 的电子邮件至:
http://myapp:8080/passwordreset?code=CfDJ8JBnWaVj6h1PtqlmlJaH57r9TRA5j7Ij1BVyeBUpqX+5Cq1msu9zgkuI32Iz9x/5uE1B9fKFp4tZFFy6lBTseDFTHSJxwtGu+jHX5cajptUBiVqIChiwoTODh7ei4+MOkX7rdNVBMhG4jOZWqqtZ5J30gXr/JmltbYxqOp4JLs8V05BeKDbbVO/Fsq5+jebokKkR5HEJU+mQ5MLvNURsJKRBbI3qIllj1RByXt9mufGRE3wmQf2fgKBkAL6VsNgB8w==
然后我的 AngularJs 应用程序将呈现一个带有表单的视图以输入和确认新密码,然后将一个 JSON 对象与新密码和从 URL.
中的查询参数中获取的代码
最后我的后端会收到 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(); }
问题是 Identity 响应
Invalid token
错误。我发现问题是代码不匹配,上面的代码将在 PUT 请求的 JSON 对象中收到,如下所示:
CfDJ8JBnWaVj6h1PtqlmlJaH57r9TRA5j7Ij1BVyeBUpqX 5Cq1msu9zgkuI32Iz9x/5uE1B9fKFp4tZFFy6lBTseDFTHSJxwtGu jHX5cajptUBiVqIChiwoTODh7ei4 MOkX7rdNVBMhG4jOZWqqtZ5J30gXr/JmltbYxqOp4JLs8V05BeKDbbVO/Fsq5 jebokKkR5HEJU mQ5MLvNURsJKRBbI3qIllj1RByXt9mufGRE3wmQf2fgKBkAL6VsNgB8w==
请注意,以前有 +
符号的地方现在有空格 符号,显然这会导致 Identity 认为标记不同。
出于某种原因 Angular 正在以不同的编码方式解码 URL 查询参数。
如何解决?
这个答案 为我指明了正确的方向。但正如我所说,它适用于不同的版本,现在它的解决方案略有不同。
答案还是一样:用 base 64 url 编码 token,然后用 base 64 url 解码它。这样 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);
}
我有一个类似的问题,我正在编码我的令牌,但它一直在验证失败,结果问题是这样的:options.LowercaseQueryStrings = true;
不要在 options.LowercaseQueryStrings
上设置 true
这会改变验证令牌的完整性,您将收到无效令牌错误。
// This allows routes to be in lowercase
services.AddRouting(options =>
{
options.LowercaseUrls = true;
options.LowercaseQueryStrings = false;
});
在我的 Asp.Net Core 3.0 项目中构建 ConfirmEmail 页面后,我 运行 遇到了同样的问题。
从 ConfirmEmail.cshtml.cs
中的 OnGetAsync
方法中删除以下行修复了问题:
code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
在脚手架登录页面中,code
被添加到 callbackUrl
,然后 URL 使用 HtmlEncoder.Default.Encode(callbackUrl)
编码。单击 link 时,解码会自动完成,code
就像确认电子邮件一样。
更新:
我注意到在 忘记密码 过程中 code
在被放入 callbackUrl
之前进行了 Base64 编码,这意味着 Base64 解码 是必要的。
因此,更好的解决方案是在将代码添加到 callbackUrl 之前将以下行添加到生成代码的任何位置。
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
这里是 link 到 issue 已经修复。
我已经尝试了上面的答案,但是这个 guide 对我有帮助。基本上,您需要对代码进行编码,否则,您会遇到一些奇怪的错误。总而言之,您需要这样做:
string code = HttpUtility.UrlEncode(UserManager.GenerateEmailConfirmationToken(userID));
在此之后,如果适用于您,解码代码:
string decoded = HttpUtility.UrlDecode(code)
(根据这个post:)
对于 resetPasswordAsync (identity manager)“token invalid”问题...因为“+”变成了space在 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>.";
现在您可以点击您的 link,然后您将使用“+”(按 %2B 编码)转到好的 url...您的令牌不会无效。 ..
ASP Core 2.1 也有类似的问题,让我摸不着头脑,因为 code
(token
) 的 encoding/decoding 对我不起作用。而且我总是遇到 userManager.ConfirmEmailAsync(user, code)
解决方案:
事实证明,问题是用户不是使用 UserManager
创建的,而是使用 dbcontext 像 _dbContext.Users.AddAsync
在用 _userManager.CreateAsync
替换此创建方法后一切正常即使 code
(token
).
在我的例子中,这个问题是由于 RegisterModel 中的 OnPostAsync 方法编码回调 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 '&' 变成 '&',从而使整个 link.
你也可以在验证reset put中的令牌时使用正则表达式。
var decode = token.Replace(" ", "+");
await _userManager.ResetPasswordAsync(user, decode, Password);
我在 GCP 中使用 Cloud 运行 托管我的网站时遇到了同样的问题,这里的 none 解决方案对我有用。
在我的例子中,问题出在实例本地存储的数据保护密钥上。日志中的以下条目暗示了问题:
Storing keys in a directory '/home/.aspnet/DataProtection-Keys' that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed.
因此,令牌仅对颁发它们的实例有效,当用户单击发送到他们电子邮件的 link 时,该实例已经被销毁。
解决方案是为密钥使用分布式存储,例如,通过 EntityFramework:
的数据库using Microsoft.AspNetCore.DataProtection;
...
public void ConfigureServices(IServiceCollection services)
{
services.AddDataProtection()
.PersistKeysToDbContext<DbContext>();
services.AddIdentity<User, IdentityRole>()
.AddEntityFrameworkStores<AnotherDbContext>()
.AddDefaultTokenProviders();
}
可以找到不同类型存储的详细信息here。