如何使用 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,但我的要求略有不同。
- 我想使用双因素身份验证来颁发访问令牌。 (如果用户选择启用双因素身份验证)
- 我想使用 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
}
}
我看过这个 link Two Factor Auth using goolgle authenticator 关于如何在网络中创建双因素身份验证 api,但我的要求略有不同。
- 我想使用双因素身份验证来颁发访问令牌。 (如果用户选择启用双因素身份验证)
- 我想使用 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
}
}