如何使用 ASP.NET 身份在 Web API 2 中实施双重身份验证?

How to implement two factor auth in Web API 2 using ASP.NET identity?

我看过这个 link Two Factor Auth using goolgle authenticator 关于如何在网络中创建双因素身份验证 api,但我的要求略有不同。

  1. 我想使用双因素身份验证来颁发访问令牌。 (如果用户选择启用双因素身份验证)
  2. 我想使用 ASP.NET 身份本身创建 OTP 代码。 (就像我们在 MVC 网络应用程序中所做的那样 SignInManager.SendTwoFactorCodeAsync("Phone Code")

我当前实施的问题是,当我调用 SignInManager.SendTwoFactorCodeAsync("Phone Code") 时,我得到错误用户 ID 未找到。

为了调试,我尝试调用 User.Identity.GetUserId(); 并且它 returns 是正确的用户 ID。

我查看了Microsoft.AspNet.Identity.Owin程序集的源代码

    public virtual async Task<bool> SendTwoFactorCodeAsync(string provider)
    {
        var userId = await GetVerifiedUserIdAsync().WithCurrentCulture();
        if (userId == null)
        {
            return false;
        }

        var token = await UserManager.GenerateTwoFactorTokenAsync(userId, provider).WithCurrentCulture();
        // See IdentityConfig.cs to plug in Email/SMS services to actually send the code
        await UserManager.NotifyTwoFactorTokenAsync(userId, provider, token).WithCurrentCulture();
        return true;
    }

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

从上面的代码可以看出,SendTwoFactorCodeAsync 方法在内部调用 GetVerifiedUserIdAsync 检查双因素身份验证 cookie。由于这是一个 web api 项目,cookie 不存在并返回 0,导致找不到用户 ID 错误。

我的问题是,如何使用 asp.net 身份在 Web api 中正确实施双重身份验证?

这就是我为了在 api 上运行而实施的。我假设您使用的是默认的 ASP.NET 单用户模板。

1. ApplicationOAuthProvider

GrantResourceOwnerCredentials 方法中,您必须添加此代码

var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);

var twoFactorEnabled = await userManager.GetTwoFactorEnabledAsync(user.Id);
if (twoFactorEnabled)
{
 var code = await userManager.GenerateTwoFactorTokenAsync(user.Id, "PhoneCode");
 IdentityResult notificationResult = await userManager.NotifyTwoFactorTokenAsync(user.Id, "PhoneCode", code);
 if(!notificationResult.Succeeded){
   //you can add your own validation here
   context.SetError(error, "Failed to send OTP"); 
 }
}

// commented for clarification
ClaimIdentity oAuthIdentity .....

// Commented for clarification
AuthenticationProperties properties = CreateProperties(user);
// Commented for clarification

CreateProperties 方法中用 userObject 替换参数,如下所示:

public static AuthenticationProperties CreateProperties(ApplicationUser user)
{
  IDictionary<string, string> data = new Dictionary<string, string>
  {
    { "userId", user.Id },
    { "requireOTP" , user.TwoFactorEnabled.ToString() },
  }

// commented for clarification
}

以上代码检查用户是否启用了 TFA,如果启用,它将生成验证码并使用您选择的 SMSService 发送。

2。创建 TwoFactorAuthorize 属性

创建响应 class ResponseData

public class ResponseData
{
    public int Code { get; set; }
    public string Message { get; set; }
}

添加 TwoFactorAuthorizeAttribute

public override async Task OnAuthorizationAsync(HttpActionContext actionContext, System.Threading.CancellationToken cancellationToken)
    {
        #region Get userManager
        var userManager = HttpContext.Current.GetOwinContext().Get<ApplicationUserManager>();
        if(userManager == null)
        {
            actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized, new ResponseData
            {
                Code = 100,
                Message = "Failed to authenticate user."
            });
            return;
        }
        #endregion

        var principal = actionContext.RequestContext.Principal as ClaimsPrincipal;

        #region Get current user
        var user = await userManager.FindByNameAsync(principal?.Identity?.Name);
        if(user == null)
        {
            actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized, new ResponseData
            {
                Code = 100,
                Message = "Failed to authenticate user."
            });
            return;
        }
        #endregion

        #region Validate Two-Factor Authentication
        if (user.TwoFactorEnabled)
        {
            actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized, new ResponseData
            {
                Code = 101,
                Message = "User must be authenticated using Two-Factor Authentication."
            });
        }
        #endregion

        return;
    }
}

3。使用 TwoFactorAuthorizeAttribute

在控制器中使用 TwoFactorAuthorizeAttribute

[Authorize]
[TwoFactorAuthorize]
public IHttpActionResult DoMagic(){
}

4.验证 OTP 在您的 AccountController 中,您必须添加 api 端点以验证 OTP

    [Authorize]
    [HttpGet]
    [Route("VerifyPhoneOTP/{code}")]
    public async Task<IHttpActionResult> VerifyPhoneOTP(string code)
    {
        try
        {
           bool verified = await UserManager.VerifyTwoFactorTokenAsync(User.Identity.GetUserId(), "PhoneCode", code);
            if (!verified)
                return BadRequest($"{code} is not a valid OTP, please verify and try again.");


            var result = await UserManager.SetTwoFactorEnabledAsync(User.Identity.GetUserId(), false);
            if (!result.Succeeded)
            {
                foreach (string error in result.Errors)
                    errors.Add(error);

                return BadRequest(errors[0]);
            }

            return Ok("OTP verified successfully.");
        }
        catch (Exception exception)
        {
            // Log error here
        }
    }