How to implement two auth factors in Web API 2 using ASP.NET identifier?

I saw this Two Factor Auth link using goolgle authenticator on how to create two-factor authentication in web api, but my requirements are a little different.

  • I want to use two-factor authentication to issue an access token. (If the user decides to enable two-factor authentication)
  • I would like to create OTP code using ASP.NET itself. (Similar to how we do this in the MVC SignInManager.SendTwoFactorCodeAsync("Phone Code") web application SignInManager.SendTwoFactorCodeAsync("Phone Code")

The problem with my current implementation, when I call SignInManager.SendTwoFactorCodeAsync("Phone Code") , I get the user ID not found error.

For debugging, I tried calling User.Identity.GetUserId(); and returned the correct user id.

I checked the assembly source code of 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); } 

As you can see from the above code, the SendTwoFactorCodeAsync method internally calls GetVerifiedUserIdAsync , which checks the cookie with two factors. Since this is a web api project, a cookie does not exist and 0 is returned, resulting in a user ID not found.

My question is, how to properly implement two-factor authentication in web api using asp.net id?

+5
source share
1 answer

This is what I implemented to make this work on api. I assume that you are using the default ASP.NET single user template.

1. ApplicationOAuthProvider

inside the GrantResourceOwnerCredentials method you must add this code

 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 

Inside the CreateProperties method, replace paramenter with userObject as follows:

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

The above code will check if the TFA user is allowed, if enabled, he will generate a confirmation code and send it using SMSService of your choice.

2. Create an attribute TwoFactorAuthorize

create response class ResponseData strong>

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

add 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. Use the TwoFactorAuthorizeAttribute attribute

controller uses TwoFactorAuthorizeAttribute

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

4. Check OTP In your AccountController you must add an api endpoint to check 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 } } 
+5
source

Source: https://habr.com/ru/post/1266537/


All Articles