在同一请求中调用PasswordSignIn和SendTwoFactorCode

时间:2018-11-22 00:55:14

标签: asp.net-mvc-4 asp.net-identity two-factor-authentication

我想在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正常工作?

1 个答案:

答案 0 :(得分:5)

  

在调用PasswordSignIn之后,是否可以立即使SendTwoFactorCode正常工作?

简短答案:

建议的替代方法:

文档中通常建议的流程是

  1. 通过用户名和密码对用户进行身份验证。
  2. 如果用户名和密码有效,并且由于启用了2FA而需要进行其他验证,则重定向到该页面以发送代码。
  3. 如果用户开始发送代码,则会发送代码,然后将用户重定向到验证页面。

第二步通常是确认提供者(如果有多个)(例如SMS,电子邮件等),以用于第二因素验证。

例如,以下操作对RequiresVerification结果进行重定向

帐户/登录

//...

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

但是,由于您已经确定要通过电子邮件发送代码,因此您可以跳过第二步,直接重定向到验证代码,该代码就是发送和验证代码的位置。

帐户/验证码

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

Source

就像您指示时一样

  

如果我再次发出请求,则对SendTwoFactorCode的调用将按预期工作。

第二个请求很重要,因为它将包括上一个请求中设置的cookie。

引用How SignInManager checks for 2FA requirement