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

时间:2017-04-10 07:45:32

标签: authentication asp.net-web-api asp.net-identity one-time-password

我已经看到了关于如何在web api中创建双因素身份验证的链接Two Factor Auth using goolgle authenticator,但我的要求却略有不同。

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

    我当前实现的问题是,当我调用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错误。

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

1 个答案:

答案 0 :(得分:12)

这是我为实现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属性

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