Background
I'm building an API with .NET 6 where I'm currently trying to implement two factor authentication using Google Authenticator. It seems to be working fine up until the final part where I actually try to sign in using the code provided by the authenticator app.
Code
2FA Setup - Part 1:
[HttpGet("TwoFactorAuthSetup/{email}")]
public async Task<IActionResult> GetTwoFactorAuthSetup(string email)
{
...
var isTwoFactorAuthEnabled = await _userManager.GetTwoFactorEnabledAsync(user);
var authenticatorKey = await _userManager.GetAuthenticatorKeyAsync(user);
//var authenticatorKey = await _userManager.GenerateNewAuthenticatorKey(user);
if (authenticatorKey == null)
{
await _userManager.ResetAuthenticatorKeyAsync(user);
authenticatorKey = await _userManager.GetAuthenticatorKeyAsync(user);
}
...
2FA Setup - Part 2:
[HttpPost("TwoFactorAuthSetup")]
public async Task<IActionResult> SetupTwoFactorAuth([FromBody] TwoFactorAuthSetupDto model)
{
var user = await _userManager.FindByEmailAsync(model.Email);
var isCodeValid = await _userManager
.VerifyTwoFactorTokenAsync(user,
_userManager.Options.Tokens.AuthenticatorTokenProvider,
model.Code);
if (!isCodeValid)
{
return BadRequest("Invalid code");
}
await _userManager.SetTwoFactorEnabledAsync(user, true);
...
2FA Login:
[HttpPost("login-2fa")]
[AllowAnonymous]
public async Task<IActionResult> LoginWithTwoFactorAuth(TwoFactorAuthSetupDto model)
{
var user = await _userManager.FindByEmailAsync(model.Email);
//user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
{
return Unauthorized();
}
//var isValid = await _userManager.VerifyTwoFactorTokenAsync(user, _userManager.Options.Tokens.AuthenticatorTokenProvider, model.Code);
var result = await _signInManager.TwoFactorSignInAsync(_userManager.Options.Tokens.AuthenticatorTokenProvider, model.Code, true, false);
var result2 = await _signInManager.TwoFactorAuthenticatorSignInAsync( model.Code, true, false);
Both 2FA Setup - Part 1 and 2FA Setup - Part 2 work as expected; I'm able to successfully setup the authenticatorKey in my app, and the 6-digit code gets accepted by my controller.
The issue arises when trying to sign in using the authenticator app. Both result and result2 always end up with Succeeded = false. When I try to use the _userManager.VerifyTwoFactorTokenAsync
method instead, it does show me that the code I use is valid. I'm unsure of how to proceed.
I figured it out. In case you have a similar issue, consider the following two steps -
Use _signInManager.PasswordSignInAsync
to sign in the user. This drops a cookie that helps remember the user in case they had ticked the remember me.
Use var user = _signInManager.GetTwoFactorAuthenticationuserAsync()
which checks for the presence of the Identity.TwoFactorUserId
cookie.
I made few mistakes earlier:
_signInManager.CheckPasswordSignInAsync
instead of _signInManager.PasswordSignInAsync
.var user = await _userManager.FindByEmailAsync(model.Email);
to get the user for which 2 factor auth login had to be performed. This provides a security risk. I since replaced this line with await _signInManager.GetTwoFactorAuthenticationUserAsync()
.With the changes mentioned above it now works as intended.