Asp.net 核心身份变更 username/email
Asp.net core identity change username/email
带有确认逻辑的默认身份更改 username/email 没有意义。
- 设置需要电子邮件确认的应用程序。
- 设置需要确认电子邮件才能登录。
- 用户然后更改电子邮件,输入错误的电子邮件,注销。
- 现在用户被锁定了。电子邮件已更改但需要
确认登录,没有电子邮件确认 link 因为
地址输入错误。
我的应用程序设置有误还是 Microsoft 没有很好地设计 Identity?
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
//...
var email = await _userManager.GetEmailAsync(user);
if (Input.Email != email)
{
var setEmailResult = await _userManager.SetEmailAsync(user, Input.Email);
if (!setEmailResult.Succeeded)
{
var userId = await _userManager.GetUserIdAsync(user);
throw new InvalidOperationException($"Unexpected error occurred setting email for user with ID '{userId}'.");
}
StatusMessage = "<strong>Verify your new email</strong><br/><br/>" +
"We sent an email to " + Input.Email +
" to verify your address. Please click the link in that email to continue.";
}
//...
await _signInManager.RefreshSignInAsync(user);
return RedirectToPage();
}
您的问题是将 SetEmailAsync
用于此目的。当 none 当前 存在时,该方法旨在为用户 设置电子邮件。在这种情况下,将 confirmed 设置为 false 是有意义的,不会造成任何问题。
还有另一种方法,ChangeEmailAsync
,这是您应该使用的方法。此方法需要一个令牌,该令牌将从电子邮件确认流程中获取。换句话说,您应该执行的步骤是:
- 用户提交带有新电子邮件的表单以更改为
- 您向用户发送确认邮件。用户要更改的电子邮件地址需要保存在确认 link 中或数据库中的单独位置。换句话说,用户在他们的用户记录中的实际电子邮件没有改变。
- 用户点击电子邮件中的确认 link。您从 link 或您之前保存的任何地方获得他们想要更改的新电子邮件地址
- 您使用此电子邮件和来自确认 link 的令牌致电
ChangeEmailAsync
。
- 用户的电子邮件现已更改并确认。
编辑
FWIW,是的,这似乎是默认模板的问题。不知道他们为什么这样做,因为是的,它会破坏很多东西,就像我在回答中说的那样,ChangeEmailAsync
就是为了这个目的而存在的。只需按照我上面概述的步骤并更改此处的逻辑,以了解当用户通过“管理”页面提交新电子邮件地址时发生的情况。
编辑 #2
我已经为此提交了 issue on Github。我现在不能再花时间在这上面了,但是如果我有时间并且没有其他人抢我的话,我会尝试提交一个 pull request 来修复。修复相对简单。
编辑 #3
我能够在 fork 中获得基本的电子邮件更改流程。然而,该团队已经分配了这个问题,并且似乎将其作为身份 UI 更大规模改革的一部分。我现在可能不会再花时间讨论这个问题,但鼓励您关注该问题以获取团队的更新。如果您现在碰巧借用了我的代码来实施修复,请注意,我正在尝试创建一个对其他代码的熵最少的解决方案。在实际的生产应用程序中,您应该将新电子邮件保存在数据库中的某个位置,而不是在 URL 中传递它。
如前所述,模板肯定提供了错误的行为。您可以在 https://github.com/aspnet/Scaffolding repo here 中查看模板的源代码。
我建议在 GitHub 项目上提出问题,以便更改。更新模板时,毫无疑问,它们必须考虑启用确认和未启用确认的情况。在您的情况下,您可以相对轻松地重用 OnPostSendVerificationEmailAsync()
中已经存在的逻辑。
一个更通用的实现看起来像这样:
public partial class IndexModel : PageModel
{
// inject as IOptions<IdentityOptions> into constructor
private readonly IdentityOptions _options;
// Extracted from OnPostSendVerificationEmailAsync()
private async Task SendConfirmationEmail(IdentityUser user, string email)
{
var userId = await _userManager.GetUserIdAsync(user);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { userId = userId, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(
email,
"Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
}
public async Task<IActionResult> OnPostAsync()
{
//... Existing code
var email = await _userManager.GetEmailAsync(user);
var confirmationEmailSent = false;
if (Input.Email != email)
{
if(_options.SignIn.RequireConfirmedEmail)
{
// new implementation
await SendConfirmationEmail(user, Input.Email);
confirmationEmailSent = true;
}
else
{
// current implementation
var setEmailResult = await _userManager.SetEmailAsync(user, Input.Email);
if (!setEmailResult.Succeeded)
{
var userId = await _userManager.GetUserIdAsync(user);
throw new InvalidOperationException($"Unexpected error occurred setting email for user with ID '{userId}'.");
}
}
var setEmailResult = await _userManager.SetEmailAsync(user, Input.Email);
if (!setEmailResult.Succeeded)
{
var userId = await _userManager.GetUserIdAsync(user);
throw new InvalidOperationException($"Unexpected error occurred setting email for user with ID '{userId}'.");
}
}
// existing update phone number code;
await _signInManager.RefreshSignInAsync(user);
StatusMessage = confirmationEmailSent
? "Verification email sent. Please check your email."
: "Your profile has been updated";
return RedirectToPage();
}
public async Task<IActionResult> OnPostSendVerificationEmailAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var email = await _userManager.GetEmailAsync(user);
await SendConfirmationEmail(user, email);
StatusMessage = "Verification email sent. Please check your email.";
return RedirectToPage();
}
}
带有确认逻辑的默认身份更改 username/email 没有意义。
- 设置需要电子邮件确认的应用程序。
- 设置需要确认电子邮件才能登录。
- 用户然后更改电子邮件,输入错误的电子邮件,注销。
- 现在用户被锁定了。电子邮件已更改但需要 确认登录,没有电子邮件确认 link 因为 地址输入错误。
我的应用程序设置有误还是 Microsoft 没有很好地设计 Identity?
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
//...
var email = await _userManager.GetEmailAsync(user);
if (Input.Email != email)
{
var setEmailResult = await _userManager.SetEmailAsync(user, Input.Email);
if (!setEmailResult.Succeeded)
{
var userId = await _userManager.GetUserIdAsync(user);
throw new InvalidOperationException($"Unexpected error occurred setting email for user with ID '{userId}'.");
}
StatusMessage = "<strong>Verify your new email</strong><br/><br/>" +
"We sent an email to " + Input.Email +
" to verify your address. Please click the link in that email to continue.";
}
//...
await _signInManager.RefreshSignInAsync(user);
return RedirectToPage();
}
您的问题是将 SetEmailAsync
用于此目的。当 none 当前 存在时,该方法旨在为用户 设置电子邮件。在这种情况下,将 confirmed 设置为 false 是有意义的,不会造成任何问题。
还有另一种方法,ChangeEmailAsync
,这是您应该使用的方法。此方法需要一个令牌,该令牌将从电子邮件确认流程中获取。换句话说,您应该执行的步骤是:
- 用户提交带有新电子邮件的表单以更改为
- 您向用户发送确认邮件。用户要更改的电子邮件地址需要保存在确认 link 中或数据库中的单独位置。换句话说,用户在他们的用户记录中的实际电子邮件没有改变。
- 用户点击电子邮件中的确认 link。您从 link 或您之前保存的任何地方获得他们想要更改的新电子邮件地址
- 您使用此电子邮件和来自确认 link 的令牌致电
ChangeEmailAsync
。 - 用户的电子邮件现已更改并确认。
编辑
FWIW,是的,这似乎是默认模板的问题。不知道他们为什么这样做,因为是的,它会破坏很多东西,就像我在回答中说的那样,ChangeEmailAsync
就是为了这个目的而存在的。只需按照我上面概述的步骤并更改此处的逻辑,以了解当用户通过“管理”页面提交新电子邮件地址时发生的情况。
编辑 #2
我已经为此提交了 issue on Github。我现在不能再花时间在这上面了,但是如果我有时间并且没有其他人抢我的话,我会尝试提交一个 pull request 来修复。修复相对简单。
编辑 #3
我能够在 fork 中获得基本的电子邮件更改流程。然而,该团队已经分配了这个问题,并且似乎将其作为身份 UI 更大规模改革的一部分。我现在可能不会再花时间讨论这个问题,但鼓励您关注该问题以获取团队的更新。如果您现在碰巧借用了我的代码来实施修复,请注意,我正在尝试创建一个对其他代码的熵最少的解决方案。在实际的生产应用程序中,您应该将新电子邮件保存在数据库中的某个位置,而不是在 URL 中传递它。
如前所述,模板肯定提供了错误的行为。您可以在 https://github.com/aspnet/Scaffolding repo here 中查看模板的源代码。
我建议在 GitHub 项目上提出问题,以便更改。更新模板时,毫无疑问,它们必须考虑启用确认和未启用确认的情况。在您的情况下,您可以相对轻松地重用 OnPostSendVerificationEmailAsync()
中已经存在的逻辑。
一个更通用的实现看起来像这样:
public partial class IndexModel : PageModel
{
// inject as IOptions<IdentityOptions> into constructor
private readonly IdentityOptions _options;
// Extracted from OnPostSendVerificationEmailAsync()
private async Task SendConfirmationEmail(IdentityUser user, string email)
{
var userId = await _userManager.GetUserIdAsync(user);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { userId = userId, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(
email,
"Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
}
public async Task<IActionResult> OnPostAsync()
{
//... Existing code
var email = await _userManager.GetEmailAsync(user);
var confirmationEmailSent = false;
if (Input.Email != email)
{
if(_options.SignIn.RequireConfirmedEmail)
{
// new implementation
await SendConfirmationEmail(user, Input.Email);
confirmationEmailSent = true;
}
else
{
// current implementation
var setEmailResult = await _userManager.SetEmailAsync(user, Input.Email);
if (!setEmailResult.Succeeded)
{
var userId = await _userManager.GetUserIdAsync(user);
throw new InvalidOperationException($"Unexpected error occurred setting email for user with ID '{userId}'.");
}
}
var setEmailResult = await _userManager.SetEmailAsync(user, Input.Email);
if (!setEmailResult.Succeeded)
{
var userId = await _userManager.GetUserIdAsync(user);
throw new InvalidOperationException($"Unexpected error occurred setting email for user with ID '{userId}'.");
}
}
// existing update phone number code;
await _signInManager.RefreshSignInAsync(user);
StatusMessage = confirmationEmailSent
? "Verification email sent. Please check your email."
: "Your profile has been updated";
return RedirectToPage();
}
public async Task<IActionResult> OnPostSendVerificationEmailAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var email = await _userManager.GetEmailAsync(user);
await SendConfirmationEmail(user, email);
StatusMessage = "Verification email sent. Please check your email.";
return RedirectToPage();
}
}