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

Authentication 如何使用ASP.NET标识在Web API 2中实现双因素身份验证?,authentication,asp.net-web-api,asp.net-identity,one-time-password,Authentication,Asp.net Web Api,Asp.net Identity,One Time Password,我已经看到了这个关于如何在WebAPI中创建双因素身份验证的链接,但是我的要求没有什么不同 我想使用双因素身份验证来颁发访问令牌。(如果用户选择启用双因素身份验证) 我想使用ASP.NET标识本身创建OTP代码。(就像我们在MVC web应用程序中所做的那样SignInManager.sendtowfactorcodeasync(“电话代码”) 我当前实现的问题是,当我调用SignInManager.SendTwoFactorCodeAsync(“电话代码”)时,我得到了找不到用户id的错误信息

我已经看到了这个关于如何在WebAPI中创建双因素身份验证的链接,但是我的要求没有什么不同

  • 我想使用双因素身份验证来颁发访问令牌。(如果用户选择启用双因素身份验证)
  • 我想使用ASP.NET标识本身创建OTP代码。(就像我们在MVC web应用程序中所做的那样
    SignInManager.sendtowfactorcodeasync(“电话代码”)
  • 我当前实现的问题是,当我调用
    SignInManager.SendTwoFactorCodeAsync(“电话代码”)
    时,我得到了找不到用户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(字符串提供程序)
    {
    var userId=wait GetVerifiedUserIdAsync().WithCurrentCulture();
    if(userId==null)
    {
    返回false;
    }
    var token=await UserManager.GenerateTwoFactorTokenAsync(userId,provider).WithCurrentCulture();
    //请参阅IdentityConfig.cs以插入电子邮件/短信服务以实际发送代码
    等待UserManager.NotifyTwoFactoryTokenAsync(用户ID、提供程序、令牌).WithCurrentCulture();
    返回true;
    }
    公共异步任务GetVerifiedUserIdAsync()
    {
    var result=wait AuthenticationManager.authenticateSync(DefaultAuthenticationTypes.TwoFactorCookie).WithCurrentCulture();
    if(result!=null&&result.Identity!=null&&String.IsNullOrEmpty(result.Identity.GetUserId())
    {
    返回ConvertIdFromString(result.Identity.GetUserId());
    }
    返回默认值(TKey);
    }
    
    从上面的代码中可以看出,
    SendTwoFactorCodeAsync
    方法在内部调用检查双因素身份验证cookie的
    GetVerifiedUserIdAsync
    。由于这是一个web api项目,cookie不存在,返回0,导致用户id未找到错误


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

    这是我在api上实现此功能的方法。我假设您使用的是默认的asp.net单用户模板

    1.ApplicationAuthProvider

    在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
    
    添加TwofactoraAuthorizeAttribute

    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;
        }
    }
    
    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
            }
        }
    
    [授权]
    [HttpGet]
    [路由(“VerifyPhoneOTP/{code}”)]
    公共异步任务VerifyPhoneOTP(字符串代码)
    {
    尝试
    {
    bool verified=wait UserManager.VerifyTwoFactorTokenAsync(User.Identity.GetUserId(),“PhoneCode”,code);
    如果(!已验证)
    return BadRequest($“{code}不是有效的OTP,请验证并重试。”);
    var result=await UserManager.setTwoFactoryEnabledAsync(User.Identity.GetUserId(),false);
    如果(!result.successed)
    {
    foreach(result.Errors中的字符串错误)
    错误。添加(错误);
    返回错误请求(错误[0]);
    }
    返回Ok(“OTP验证成功”);
    }
    捕获(异常)
    {
    //此处记录错误
    }
    }
    
    Hi Spharah,非常感谢您提供的详细答案。您还可以包括验证用户输入的OTP代码的逻辑吗?Hi Anand,我更新了答案以包含验证OTP的代码,别忘了对答案进行投票:-)Spharah,我已经投票了。在接受答案之前,请先澄清疑问。OTP验证成功后,将IsTwoFactorEnabled设置为false。比方说,用户在另一台机器上再次登录,现在他将无法获得OTP(对吗?),因为IstwoFactoryEnabled设置为false。你什么时候重新启用它?Anand,你是对的,这是我正在开发的应用程序的要求。要在每次用户登录时持续要求用户输入OTP,您必须更改TwoFactorAuthorization代码。我会为这种情况准备解决方案。PEO,不,我没有。
    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;
        }
    }
    
    [Authorize]
    [TwoFactorAuthorize]
    public IHttpActionResult DoMagic(){
    }
    
        [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
            }
        }