Thursday, December 21, 2023

Two Factor Authentication using .Net Core

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.



No comments:

Post a Comment

Two Factor Authentication using .Net Core

Install Package dotnet add package GoogleAuthenticator --version 3.1.1 Model Changes public bool IsAuthenticatorReset { get; set; } public s...