如何在使用ASP.NET身份的Web API 2中实现双因素验证?

问题描述:

我已经看到这个链接Two Factor Auth using goolgle authenticator关于如何在web api中创建一个双因素身份验证,但我的要求有点不同。如何在使用ASP.NET身份的Web API 2中实现双因素验证?

  1. 我想使用双因素身份验证发出访问令牌。 (如果用户选择启用双因素身份验证)
  2. 我想使用ASP.NET身份本身创建OTP代码。 (喜欢的方式,我们做的MVC Web应用程序SignInManager.SendTwoFactorCodeAsync("Phone Code")

我当前实现,当我打电话SignInManager.SendTwoFactorCodeAsync("Phone Code")我得到错误的用户ID的问题没有找到。

要调试,我试着打电话User.Identity.GetUserId();和它返回正确的用户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找不到错误。

我的问题,如何在web api中使用asp.net标识正确实现双因素身份验证?

这就是我已经实现了这个工作的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更换paramenter这样的:

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属性

创建响应类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 
     } 
    } 
+0

Spharah嗨,非常感谢您的详细解答。您是否也可以包含用于验证用户输入的OTP代码的逻辑? –

+0

嗨阿南德,我更新了答案,包括代码来验证OTP,不要忘记给予好评的答案:-) – Spharah

+0

Spharah,我已经投了。在接受答案之前,你可以通过疑问来澄清。成功的OTP验证后,您将IsTwoFactorEnabled设置为false。假设用户在另一台机器上再次登录,现在他不会得到OTP(正确?),因为IsTwoFactorEnabled设置为false。你什么时候重新启用它? –