在同一请求中调用 PasswordSignIn 和 SendTwoFactorCode
Calling PasswordSignIn and SendTwoFactorCode in the same request
我想在 asp.net mvc 中为双因素身份验证实现以下流程:
var res = sign.PasswordSignIn("myusername", "mypassword", false, false);
if(res == SignInStatus.RequiresVerification)
sign.SendTwoFactorCode("EmailCode");
但是我发现 SendTwoFactorCode
函数返回 false 并且不发送电子邮件,因为它在内部检查用户是否已通过验证。 See this line in the source. 如果我发出第二个请求,对 SendTwoFactorCode
的调用会如我所料。
有没有办法让 SendTwoFactorCode
在调用 PasswordSignIn
后立即正常工作?
Is there a way to make SendTwoFactorCode work correctly immediately after a call to PasswordSignIn?
简答:否
建议的替代方案:
文档中通常建议的流程是
- 通过用户名和密码验证用户。
- 如果用户名和密码有效,并且由于启用了 2FA 需要额外验证,然后重定向到页面以发送代码。
- 如果用户发起代码发送,代码将被发送,然后用户将被重定向到验证页面。
第二步通常是确认提供者,如果有多个(即短信、电子邮件等),用于第二因素验证。
例如,下面的代码对 RequiresVerification
结果
进行了重定向
Account/Login
//...
var result = await signInManager.PasswordSignInAsync(username, password, false, false);
switch (result) {
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("VerifyCode", new { ReturnUrl = returnUrl });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
但是由于您已经确定要通过电子邮件发送验证码,因此您可以跳过第二步并直接重定向到验证码,它可以是验证码发送和验证的地方。
Account/VerifyCode
[AllowAnonymous]
public async Task<ActionResult> VerifyCode(string returnUrl) {
var provider = "EmailCode";
// Require that the user has already logged in via username/password
var userId = await signInManager.GetVerifiedUserIdAsync();
if (userId == null) {
return View("Error");
}
// Generate the token and send it
if(!await signInManager.SendTwoFactorCodeAsync(provider)) {
return View("Error");
}
var model = new VerifyCodeViewModel {
ReturnUrl = returnUrl
};
return View(model);
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> VerifyCode(VerifyCodeViewModel model) {
var provider = "EmailCode";
if (!ModelState.IsValid) {
return View(model);
}
var result = await signInManager.TwoFactorSignInAsync(provider, model.Code, false, false);
switch (result) {
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("VerifyCode", new { ReturnUrl = returnUrl });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
这应该允许 TwoFactorCookie
包含在下一个请求中,以便 GetVerifiedUserIdAsync
的行为符合预期。
/// <summary>
/// Get the user id that has been verified already or null.
/// </summary>
/// <returns></returns>
public async Task<TKey> GetVerifiedUserIdAsync()
{
var result = await AuthenticationManager.AuthenticateAsync(DefaultAuthenticationTypes.TwoFactorCookie).WithCurrentCulture();
if (result != null && result.Identity != null && !String.IsNullOrEmpty(result.Identity.GetUserId()))
{
return ConvertIdFromString(result.Identity.GetUserId());
}
return default(TKey);
}
就像你指出的那样
If I make a second request the call to SendTwoFactorCode works as I'm expecting.
第二个请求很重要,因为它将包含前一个请求中设置的 cookie。
我想在 asp.net mvc 中为双因素身份验证实现以下流程:
var res = sign.PasswordSignIn("myusername", "mypassword", false, false);
if(res == SignInStatus.RequiresVerification)
sign.SendTwoFactorCode("EmailCode");
但是我发现 SendTwoFactorCode
函数返回 false 并且不发送电子邮件,因为它在内部检查用户是否已通过验证。 See this line in the source. 如果我发出第二个请求,对 SendTwoFactorCode
的调用会如我所料。
有没有办法让 SendTwoFactorCode
在调用 PasswordSignIn
后立即正常工作?
Is there a way to make SendTwoFactorCode work correctly immediately after a call to PasswordSignIn?
简答:否
建议的替代方案:
文档中通常建议的流程是
- 通过用户名和密码验证用户。
- 如果用户名和密码有效,并且由于启用了 2FA 需要额外验证,然后重定向到页面以发送代码。
- 如果用户发起代码发送,代码将被发送,然后用户将被重定向到验证页面。
第二步通常是确认提供者,如果有多个(即短信、电子邮件等),用于第二因素验证。
例如,下面的代码对 RequiresVerification
结果
Account/Login
//...
var result = await signInManager.PasswordSignInAsync(username, password, false, false);
switch (result) {
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("VerifyCode", new { ReturnUrl = returnUrl });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
但是由于您已经确定要通过电子邮件发送验证码,因此您可以跳过第二步并直接重定向到验证码,它可以是验证码发送和验证的地方。
Account/VerifyCode
[AllowAnonymous]
public async Task<ActionResult> VerifyCode(string returnUrl) {
var provider = "EmailCode";
// Require that the user has already logged in via username/password
var userId = await signInManager.GetVerifiedUserIdAsync();
if (userId == null) {
return View("Error");
}
// Generate the token and send it
if(!await signInManager.SendTwoFactorCodeAsync(provider)) {
return View("Error");
}
var model = new VerifyCodeViewModel {
ReturnUrl = returnUrl
};
return View(model);
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> VerifyCode(VerifyCodeViewModel model) {
var provider = "EmailCode";
if (!ModelState.IsValid) {
return View(model);
}
var result = await signInManager.TwoFactorSignInAsync(provider, model.Code, false, false);
switch (result) {
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("VerifyCode", new { ReturnUrl = returnUrl });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
这应该允许 TwoFactorCookie
包含在下一个请求中,以便 GetVerifiedUserIdAsync
的行为符合预期。
/// <summary>
/// Get the user id that has been verified already or null.
/// </summary>
/// <returns></returns>
public async Task<TKey> GetVerifiedUserIdAsync()
{
var result = await AuthenticationManager.AuthenticateAsync(DefaultAuthenticationTypes.TwoFactorCookie).WithCurrentCulture();
if (result != null && result.Identity != null && !String.IsNullOrEmpty(result.Identity.GetUserId()))
{
return ConvertIdFromString(result.Identity.GetUserId());
}
return default(TKey);
}
就像你指出的那样
If I make a second request the call to SendTwoFactorCode works as I'm expecting.
第二个请求很重要,因为它将包含前一个请求中设置的 cookie。