Install Package
dotnet add package GoogleAuthenticator --version 3.1.1
Model Changes
public bool IsAuthenticatorReset { get; set; }
public string UniqueAuthenticatorKey { get; set; }
Method for updating AuthenticationKey
public async Task<string> UpdateUniqueAuthenticatorKey(AppUser user, string UniqueAuthenticatorKey)
{
//var user = (await _appUserRepository.List(x => x.Email == email)).FirstOrDefault();
if (user != null)
{
user.UniqueAuthenticatorKey = UniqueAuthenticatorKey;
await _userManager.UpdateAsync(user);
return "success";
}
return "error";
}
Steps ActionMethod
public async Task<IActionResult> LoginWithAuthenticatorStepOne(string email, string userName, string returnUrl)
{
try
{
var user = await _userManager.FindByEmailAsync(email);
string googleAuthKey = _configuration["ApplicationSettings:GoogleAuthKey"].ToString();
string UserUniqueKey = (user.Id.ToString() + googleAuthKey + DateTime.Now.Second.ToString() + DateTime.Now.Millisecond.ToString());
await _appUserService.UpdateUniqueAuthenticatorKey(user, UserUniqueKey);
//Two Factor Authentication Setup
TwoFactorAuthenticator TwoFacAuth = new TwoFactorAuthenticator();
var setupInfo = TwoFacAuth.GenerateSetupCode("CDS", userName, ConvertSecretToBytes(UserUniqueKey, false), 300);
ViewBag.BarcodeImageUrl = setupInfo.QrCodeSetupImageUrl;
HttpContext.Session.Remove("QrCodeSetupImageUrl");
HttpContext.Session.SetString("QrCodeSetupImageUrl", setupInfo.QrCodeSetupImageUrl);
ViewBag.SetupCode = setupInfo.ManualEntryKey;
return View(new LoginModel { Username = userName });
}
catch (Exception ex)
{
ModelState.AddModelError("Error", ex.Message);
return View(new LoginModel { Username = userName });
}
}
public async Task<IActionResult> LoginWithAuthenticatorNext(LoginModel model, string returnUrl = null)
{
var user = await _userManager.FindByNameAsync(model.Username);
if (user != null && user.IsActive)
{
return RedirectToAction("LoginWithAuthenticatorStepTwo", new { user.Email, user.UserName, returnUrl });
}
else
{
ModelState.AddModelError("Error", "Invalid or Inactive User");
return View("LoginWithAuthenticatorNext", model);
}
}
public async Task<IActionResult> LoginWithAuthenticatorStepTwo(string email, string userName, string returnUrl)
{
try
{
var user = await _userManager.FindByEmailAsync(email);
if (user != null && user.IsActive)
{
return View(new LoginModel { Username = userName });
}
else
{
ModelState.AddModelError("Error", "Invalid or Inactive User");
return View("LoginWithAuthenticatorStepTwo", new { user.Email, user.UserName, returnUrl });
}
}
catch (Exception ex)
{
ModelState.AddModelError("Error", ex.Message);
return View(new LoginModel { Username = userName });
}
}
Method for checking Authenticator
public async Task<IActionResult> TwoFactorAuthenticate(LoginModel model, string returnUrl = null)
{
ViewData["returnUrl"] = returnUrl;
var version = await _settingService.GetVersionSetting();
ViewData["downloadURL"] = version.Where(x => x.Name == "DesktopAppURL").FirstOrDefault()?.Key;
var user = await _userManager.FindByNameAsync(model.Username);
if (user != null && user.IsActive)
{
var role = await _userManager.GetRolesAsync(user);
TwoFactorAuthenticator TwoFacAuth = new TwoFactorAuthenticator();
string userUniqueKey = user.UniqueAuthenticatorKey;
bool isLoggedIn = Convert.ToBoolean(HttpContext.Session.GetString("isLoggedIn"));
bool isValid = TwoFacAuth.ValidateTwoFactorPIN(userUniqueKey, model.OTP, false);
if (isValid && isLoggedIn)
{
HttpContext.Session.SetString("isLoggedIn", "false");
await _appUserService.ResetAuthenticator(user.Id, true);
await _signInManager.SignOutAsync();
await _signInManager.SignInAsync(user, true);
var token = _tokenCreationService.CreateJWTtokenForWeb(user, role.FirstOrDefault());
ViewData["Token"] = token.Token;
ViewData["RefreshToken"] = token.Refresh_Token;
UserRefreshToken obj = new UserRefreshToken
{
RefreshToken = token.Refresh_Token,
UserName = user.UserName,
IsActive = true,
CreatedAt = DateTime.Now,
};
_jWTRepository.AddUserRefreshTokens(obj);
_jWTRepository.SaveCommit();
TempData["LoginName"] = user.FirstName + " " + user.LastName;
TempData.Keep("LoginName");
return Redirect("/Dashboard");
}
else
{
ViewBag.BarcodeImageUrl = HttpContext.Session.GetString("QrCodeSetupImageUrl");
ModelState.AddModelError("Error", "Invalid Code");
return View("LoginWithAuthenticatorStepTwo", model);
}
}
ViewBag.BarcodeImageUrl = HttpContext.Session.GetString("QrCodeSetupImageUrl");
ModelState.AddModelError("Error", "Invalid or Inactive User");
return View("LoginWithAuthenticatorStepTwo", model);
}
private static byte[] ConvertSecretToBytes(string secret, bool secretIsBase32) =>
secretIsBase32 ? Base32Encoding.ToBytes(secret) : Encoding.UTF8.GetBytes(secret);
Reset Authenticator
public async Task<bool> ResetAuthenticator(int userId, bool isReset)
{
try
{
var user = await _appUserRepository.GetById(userId);
user.IsAuthenticatorReset = isReset;
await _appUserRepository.Update(user);
return true;
}
catch
{
return false;
}
}
Note: so in this authentication, what we are achieving is, let's gather throw points:
i- Authentication setup code and qr code only setup once.
ii- if user deleted the app then he needs to again contact to the admin to reissue the qr code mean a new app.
iii- only one time QR code will show.