Two Factor Authentication

google authenticator

Using Google Authentication

Have you ever had the need to use Google Authentication, but for whatever reason, you could not use entity framework. This blog shows how to do just that but use ADO.Net with stored procedures.

The blog will also show you how to encrypt user emails and store them in your database. I will show you two ways of doing this, one using Effortless.Net.Encryption a Nuget package and then use ProtectedData Class which can be found in the System.Security.Cryptography namespace under the System.Security Assembly.

The blog will also show have to create a cookie using Owin to only allow visitors to view sections of your site when they have logged in.

The first thing that we need to do is create a project in Visual Studio, and then add the class libraries that the project will need.

For this example, I have createdthe following class libraries:

  • Web.Domain
  • Web.Model
  • Web.Security
  • Web.UI, MVC project

I then added the following packages that I required.

For this project I used:

  • Effortless.Net.Encryption
  • Owin
  • Microsoft.Owin
  • Microsoft.Owin.Host.SystemWeb
  • Microsoft.Owin.Security
  • Microsoft.Owin.Security.Cookies
  • Microsoft.Owin.Security.OAuth
  • Ninject.Web.Mvc
  • Ninject.Web.Common
  • Ninject

Once everything is set up, I created the model for the site.

Register View Model
//RegisterViewModel
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
namespace Web.Model.Register
{
    public class RegisterViewModel : DataViewModel
    {  
        [DisplayName("Username")]
        [Required(ErrorMessage = "{0} Required")]
        public string Username              { get; set; }
        [DisplayName("First Name")]
        [Required(ErrorMessage = "{0} Required")]
        public string FirstName             { get; set; }
        [DisplayName("Last Name")]
        [Required(ErrorMessage = "{0} Required")]
        public string LastName              { get; set; }
        [DisplayName("Email")]
        [Required(ErrorMessage = "{0} Required")]
        public string UserEmail             { get; set; }
        [DisplayName("Password")]
        [Required(ErrorMessage = "{0} Required")]
        public string UserPassword          { get; set; }
        [DisplayName("Re-Enter Password")]
        [Compare("UserPassword", ErrorMessage = "Passwords do not match")]
        public string CompareUserPassword   { get; set; }
    }
    public class DataViewModel
    {
        public string ClientId          { get; set; }
        public string Encryptionkey     { get; set; }
        public string VerificationToken { get; set; }
    }
}
 
//Login View Model
using System.ComponentModel;
namespace Web.Model.Login
{
    public class LoginViewModel
    {
        [DisplayName("Username")]
        public string Username { get; set; }
        [DisplayName("Password")]
        public string Password { get; set; }
        [DisplayName("2FA Code")]
        public string TfaCode { get; set; }
    }
}
 
//Profile View Model
namespace Web.Model.Profile
{
    public class UserProfileDetails
    {
        public string UserId        { get; set; }
        public string PasswordHash  { get; set; }
        public string FirstName     { get; set; }
        public string LastName      { get; set; }
    }
}

Once the models have been created, we now need to create the Controllers for the site. For this example, I had three:

  • HomeController
  • AccountController
  • BaseController

I will only show the code for the account and base controller as the home controller only displays the home page.

Account Controller
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using System.Web.Mvc;
using Google.Authenticator;
using Web.Domain.CreateAccount;
using Web.Domain.UserProfile;
using Web.Model.Login;
using Web.Model.Register;
using Web.Security.CustomIdentity;
using Web.Security.CustomIdenty;
using Web.UI.Infastructure.GuidGenerator;
namespace Web.UI.Controllers
{
    [Authorize]
    public class AccountController : BaseController
    {
        private readonly IEncryptData           _iEncryptData;
        private readonly IPasswordService       _iPasswordService;
        private readonly ICreateUserAccount     _iCreateUserAccount;
        private readonly IProfile               _iProfile;
        private readonly IValidateLogin         _iValidateLogin;
        public AccountController(IEncryptData iEncryptData, 
                                 IPasswordService iPasswordService, 
                                 ICreateUserAccount iCreateUserAccount, 
                                 IProfile iProfile, 
                                 IValidateLogin iValidateLogin)
        {
            _iEncryptData           = iEncryptData;
            _iPasswordService       = iPasswordService;
            _iCreateUserAccount     = iCreateUserAccount;
            _iProfile               = iProfile;
            _iValidateLogin         = iValidateLogin;
        }
        private const string Key = "&%$£po78AD?#";//This would be put somewhere else, just added security
        [HttpGet]
        [AllowAnonymous]
        public ActionResult Login()
        {
            return View("~/Views/Login/Login.cshtml");
        }
        [HttpPost]
        [AllowAnonymous]
        public async Task<ActionResult> Login(LoginViewModel model)
        {
            bool status = false;
            var loginUser = await _iProfile.UserProfile(model);
            bool isValidLogin = _iValidateLogin.Isvalid(loginUser.FirstName, loginUser.LastName, model.Password, loginUser.PasswordHash, loginUser.UserId, "Role User");
            if (isValidLogin)
            {
                status = true;
                TwoFactorAuthenticator tfa = new TwoFactorAuthenticator();
                string userId = _iEncryptData.WindowsEncrypted($"{loginUser.UserId}{Key}");
                var tfaCode = tfa.GenerateSetupCode("GeorgePhillipsonLtd", model.Username, userId, 300, 300);
                ViewBag.Status          = status;
                TempData["UserId"]      = userId;
                ViewBag.Message         = "Please scan QR code with your phone.";
                ViewBag.BarcodeImage    = tfaCode.QrCodeSetupImageUrl;
                return View("~/Views/Login/Login.cshtml");
            }
            ViewBag.Message     = "Username or passwaord incorrect.";
            ViewBag.Status      = status;
            return View("~/Views/Login/Login.cshtml");
        }
        [HttpGet]
        [AllowAnonymous]
        public ActionResult Register()
        {
            return PartialView("~/Views/Partials/pvRegister.cshtml");
        }
        [HttpGet]
        public ActionResult UserProfile()
        {
            if (Request.IsAuthenticated)
            {
                var identity        = (ClaimsIdentity)User.Identity;
                var user            = identity.Name;
                ViewBag.FullName    = user;
                return View("~/Views/Profile/Profile.cshtml");
            }
            return View("~/Views/Login/Login.cshtml");
        }
        [HttpPost]
        [AllowAnonymous]
        public ActionResult VerifyTfa(LoginViewModel model)
        {
            var token = model.TfaCode;
            TwoFactorAuthenticator tfa  = new TwoFactorAuthenticator();
            string userKey              = TempData["UserId"].ToString();
            bool isValid                = tfa.ValidateTwoFactorPIN(userKey, token, TimeSpan.FromMinutes(5));
            if (isValid)
            {
                return RedirectToAction("UserProfile");
            }
            ViewBag.Message = "Incorrect token value.";
            return View("~/Views/Login/Login.cshtml");
        }
        [HttpPost]
        [AllowAnonymous]
        public ActionResult Register(RegisterViewModel model)
        {
            try
            {
                if (!ModelState.IsValid) return RedirectToAction("Index", "Home");
                string clientId                 = CreateGuid.NewId();
                string clientEmail              = model.UserEmail;
                string verificationCode         = SecurityStamp.EncryptSecurityStamp("Create-Account", clientId);
                var encryptEmail                = _iEncryptData.Encrypt(model.UserEmail);
                string encryptedEmail           = encryptEmail.Item1;
                string encryptedEmailkey        = encryptEmail.Item2;
                string hashPassword             = _iPasswordService.HashPassword(model.UserPassword);
                var encryptedVerificationCode   = _iEncryptData.Encrypt(verificationCode);
                model.ClientId                  = clientId;
                model.VerificationToken         = encryptedVerificationCode.ToString();
                model.UserEmail                 = encryptedEmail;
                model.Encryptionkey             = encryptedEmailkey;
                model.UserPassword              = hashPassword;
                _iCreateUserAccount.InsertNewMember(model);
                var callbackUrl = CallbackUrl(encryptedVerificationCode.ToString());
                SendEmail(callbackUrl, clientEmail);
                TempData["Message"] = MvcHtmlString.Create("<p class=\"alert alert-danger\">Please check your email to confirm your address.</p>");
                return RedirectToAction("Index", "Home");
            }
            catch (Exception e)
            {
                TempData["Message"] = MvcHtmlString.Create("<p class=\"alert alert-danger\">" + e.Message + "</p>");
                return RedirectToAction("Index", "Home");
            }
        }
        public ActionResult ConfirmEmail(string id, string code)
        {
            return View();
        }
    }
}

Base Controller
using System.Net;
using System.Net.Mail;
using System.Web.Mvc;
namespace Web.UI.Controllers
{
    public class BaseController : Controller
    {
        // GET: Base
        public string CallbackUrl(string encryptedVerificationCode)
        {
            var routeValues = new {code = encryptedVerificationCode};
            var callbackUrl = Url.Action("ConfirmEmail", "Account", routeValues, protocol: Request.Url.Scheme);
            return callbackUrl;
        }
        public void SendEmail(string callBackUrl, string email)
        {
            var body = $"Please confirm your email address by clicking this <a href=\"{callBackUrl}\">link</a>";
            var message = new MailMessage();
            message.To.Add(new MailAddress("***", email));
            message.From = new MailAddress("***");
            message.Subject = "Please Confirm your email address";
            message.Body = string.Format(body);
            message.IsBodyHtml = true;
            using (var smtp = new SmtpClient())
            {
                var credential = new NetworkCredential
                {
                    UserName = "***",
                    Password = "***"
                };
                smtp.Credentials = credential;
                smtp.Host = "***";
                smtp.Port = 000;
                smtp.EnableSsl = false;
                smtp.Send(message);
            }
        }
    }
}

Summary

So far I have shown you the models for the site and controllers, in the next blog, I will show you how to pass the data to the database and encrypt the email and hash the password.

If you have any queries, please use the form below.

Thanks for reading.

Blog Form

 Please complete the required fields (*required)

 *
*