Two Factor Authentication

Google Authenticator

In the 1st post of this section, I showed you the controllers and models that I had created for this project. This post carries on from that post and I show you the code for passing data and retrieving data from the database using ADO.Net.

The Web.Domain layer has three classes, one for creating the user, one for retrieving user data and the third is class has a static method that only contains the connection string details from the web.config file.

The reason I create this class is so that I only need to change the code in one place and all other classes using it will use the new connection details.

DbWebConfigConnectionClass
using System.Configuration;
namespace Web.Domain
{
    public class DbWebConfigConnectionClass
    {
        public static string ConnectionString { get; } = ConfigurationManager.ConnectionStrings["DbConnection"].ConnectionString;
    }
}

The create user account class inserts the user details into the database, the method is void and I use a try-catch block to handle any errors. In this case, the stored procedure checks if the username already exists. This can also be done in the UI, but for this example, I'm doing the check in the stored procedure.

The interface is in a separate class, but I have added the code within the same code block below.

Create User Account
using Web.Model.Register;
namespace Web.Domain.CreateAccount
{
    public interface ICreateUserAccount
    {
        void InsertNewMember(RegisterViewModel model);
    }
}
using System;
using System.Data;
using System.Data.SqlClient;
using Web.Model.Register;
namespace Web.Domain.CreateAccount
{
    public class CreateUserAccount : ICreateUserAccount
    {
        public void InsertNewMember(RegisterViewModel model)
        {
            try
            {
                const string spName = "dbo.NewAccount_Insert";
                using (var cn = new SqlConnection(DbWebConfigConnectionClass.ConnectionString))
                {
                    cn.Open();
                    using (var cmd = new SqlCommand(spName, cn))
                    {
                        cmd.CommandType = CommandType.StoredProcedure;
                        cmd.Parameters.AddWithValue("@ClientId", model.ClientId);
                        cmd.Parameters.AddWithValue("@Username", model.Username);
                        cmd.Parameters.AddWithValue("@FirstName", model.FirstName);
                        cmd.Parameters.AddWithValue("@LastName", model.LastName);
                        cmd.Parameters.AddWithValue("@ClientEmail", model.UserEmail);
                        cmd.Parameters.AddWithValue("@ClientPassword", model.UserPassword);
                        cmd.Parameters.AddWithValue("@EncryptionKey", model.Encryptionkey);
                        cmd.Parameters.AddWithValue("@SecurityStamp", model.VerificationToken);
                        cmd.ExecuteNonQuery();
                    }
                }
            }
            catch (SqlException e)
            {
                if (e.Errors.Count > 0)
                {
                    throw new ApplicationException(e.Message);
                }
            }
            catch (Exception e)
            {
                throw new ApplicationException(e.ToString());
            }
        }
    }
}

The profile class returns the user details, as I have a reference to my model layer, I just need to pass in the model for the parameters saving any possible typos.

As you can see, I'm also calling the stored procedure with the async keyword to make an asynchronous call to the database. This could also have been done on the create user account class, but I wanted to show both ways.

Again I have added the interface in the code block below, but this is actually in its own class.

Profile Class
using System.Threading.Tasks;
using Web.Model.Login;
using Web.Model.Profile;
namespace Web.Domain.UserProfile
{
    public interface IProfile
    {
        Task<UserProfileDetails> UserProfile(LoginViewModel model);
    }
}
using System;
using System.Data;
using System.Data.SqlClient;
using System.Threading.Tasks;
using Web.Model.Login;
using Web.Model.Profile;
namespace Web.Domain.UserProfile
{
    public class Profile : IProfile
    {
        public async Task<UserProfileDetails> UserProfile(LoginViewModel model)
        {
            try
            {
                const string spName = "dbo.UserProfile_Select";
                using (var cn = new SqlConnection(DbWebConfigConnectionClass.ConnectionString))
                {
                    cn.Open();
                    using (var cmd = new SqlCommand(spName, cn))
                    {
                        cmd.CommandType = CommandType.StoredProcedure;
                        cmd.Parameters.AddWithValue("@Username", model.Username);
                        var rdr = await cmd.ExecuteReaderAsync(CommandBehavior.Default);
                        var data = new UserProfileDetails();
                        if (!rdr.Read())
                        {
                            //TODO update database with failed login
                            throw new InvalidOperationException("No records match that username.");
                        }
                        data.UserId         = rdr.GetString(rdr.GetOrdinal("UserId"));
                        data.FirstName      = rdr.GetString(rdr.GetOrdinal("FirstName"));
                        data.LastName       = rdr.GetString(rdr.GetOrdinal("LastName"));
                        data.PasswordHash   = rdr.GetString(rdr.GetOrdinal("PasswordHash"));
                        return data;
                    }
                }
            }
            catch (SqlException e)
            {
                throw new ApplicationException(e.ToString());
            }
            catch (Exception e)
            {
                throw new ApplicationException(e.ToString());
            }
        }
    }
}

You can see the Encrypt data class below, this class uses Effortless.Net.Encryptiona Nuget package which you can install.

You can also see that the location of the encryption key is retrieved from the web.config file. The actual location of the file is outside the web application for security reasons.

I'm also using a Tuple for returning the key and encrypted data. I could have used out parameters but for this example, I thought Tuples would be a better option.

Finally, I'm using Windows built-in encryption ProtectedData as another alternative to encrypting data.

EncryptData Class
using System;
namespace Web.Security.CustomIdentity
{
    public interface IEncryptData
    {
        Tuple<string, string> Encrypt(string data);
        string Decrypt(string data, string biv);
        string WindowsEncrypted(string text);
        string WindowsDecrypted(string text);
    }
}
using System;
using System.Configuration;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using Effortless.Net.Encryption;
namespace Web.Security.CustomIdentity
{
    public class EncryptData : IEncryptData
    {
        private string EncryptionKey()
        {
            var fileExists = File.Exists(ConfigurationManager.AppSettings["EncryptKey"]);
            if (!fileExists) throw new FileNotFoundException("FileNotFound");
            string encryptionKey;
            using (StreamReader reader = new StreamReader(ConfigurationManager.AppSettings["EncryptKey"]))
            {
                encryptionKey = reader.ReadLine();
            }
            if (string.IsNullOrEmpty(encryptionKey))
            {
                throw new NullReferenceException("TxtFileEmpty");
            }
            return encryptionKey;
        }
        public Tuple<string, string> Encrypt(string data)
        {
            byte[] iv = Bytes.GenerateIV();
            string encryptedData = Strings.Encrypt(data, Convert.FromBase64String(EncryptionKey()), iv);
            return new Tuple<string, string>(encryptedData, Convert.ToBase64String(iv));
        }
        public string Decrypt(string data, string biv)
        {
            byte[] key = Convert.FromBase64String(EncryptionKey());
            byte[] iv = Convert.FromBase64String(biv);
            string decrypted = Strings.Decrypt(data, key, iv);
            return decrypted;
        }
        //READ https://msdn.microsoft.com/en-us/library/ms995355.aspx for using this type of encryption 
        public string WindowsEncrypted(string text)
        {
            return Convert.ToBase64String(ProtectedData.Protect(Encoding.Unicode.GetBytes(text),null,DataProtectionScope.LocalMachine));
        }
        public string WindowsDecrypted(string text)
        {
            return Encoding.Unicode.GetString(ProtectedData.Unprotect(Convert.FromBase64String(text),null,DataProtectionScope.LocalMachine));
        }
    }
}

The validate login class uses the built-in Owen claims and Microsoft.ASPNet.Identity.IPasswordHasher, this class hashes the user password and verifies the password for logins.

The claims cookie is only created if the password supplied by the user matches, if not no claims are created.

ValidateLogin Class
namespace Web.Security.CustomIdentity
{
    public interface IValidateLogin
    {
        bool Isvalid(string firstName,string lastName, string password, string savedPassword, string memberId,string savedRole);
        void SignOut();
    }
}
using System.Security.Claims;
using System.Web;
using Microsoft.AspNet.Identity;
using Microsoft.Owin.Security;
using Web.Security.CustomIdenty;
namespace Web.Security.CustomIdentity
{
    public class ValidateLogin : IValidateLogin
    {
        private readonly IPasswordService _iPasswordService;
        public ValidateLogin(IPasswordService iPasswordService)
        {
            _iPasswordService = iPasswordService;
        }
        public void SignOut()
        {
            var ctx = HttpContext.Current.Request.GetOwinContext();
            var authManager = ctx.Authentication;
            authManager.SignOut("TestSite");
        }
        public bool Isvalid(string firstName, string lastName, string password, string savedPassword, string memberId, string savedRole = "user" )
        {
            var validatePassword = _iPasswordService.VerifyPassword(savedPassword, password);
            if (validatePassword)
            {
                var claimsIdentity = new ClaimsIdentity("TestSite");
                claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, savedRole));
                claimsIdentity.AddClaim(new Claim(ClaimTypes.NameIdentifier, memberId));
                claimsIdentity.AddClaim(new Claim(ClaimTypes.Name, $"{firstName} {lastName}"));
                var ctx = HttpContext.Current.Request.GetOwinContext();
                var authManager = ctx.Authentication;
                authManager.SignIn(new AuthenticationProperties { IsPersistent = false }, claimsIdentity);
                return true;
            }
            return false;
        }
    }
    public sealed class PasswordServiceAdaptor : IPasswordService, IPasswordHasher
    {
        private readonly PasswordHasher _hasher;
        public PasswordServiceAdaptor()
        {
            _hasher = new PasswordHasher();
        }
        string IPasswordHasher.HashPassword(string password)
        {
            return _hasher.HashPassword(password);
        }
        PasswordVerificationResult IPasswordHasher.VerifyHashedPassword(string hashedPassword, string providedPassword)
        {
            return _hasher.VerifyHashedPassword(hashedPassword, providedPassword);
        }
        string IPasswordService.HashPassword(string password)
        {
            return AsPasswordHasher().HashPassword(password);
        }
        bool IPasswordService.VerifyPassword(string hashedPassword, string userPassword)
        {
            var result = AsPasswordHasher().VerifyHashedPassword(hashedPassword, userPassword);
            return result == PasswordVerificationResult.Success;
        }
        private IPasswordHasher AsPasswordHasher()
        {
            return this;
        }
    }
}

The security stamp class is a simple static class that takes UTC date time and the user ID, this is stored in the database and would be used to compare the current data time against the UTC time when the user verifies their email address. If they verify their email after 1 day, then verification will fail.

Encrypt Security Stamp Class
using System;
namespace Web.Security.CustomIdentity
{
    /// <summary>
    /// Create security stamp
    /// </summary>
    public static class SecurityStamp
    {
        public static string EncryptSecurityStamp(string purpose,string id)
        {
            var concentrateString = $"{purpose}{DateTimeOffset.UtcNow}{id}";
            return concentrateString;
        }
        public static string DecryptSecurityStamp()
        {
            return null;
        }
    }
}

Summary

In this post, I carried on from the last blog and showed you the code for the encryption class, user profile and creating account classes and the password hash class. In the final post, I will show you how it all fits together.

If you have any questions on this post, please use the form below.

Blog Form

 Please complete the required fields (*required)

 *
*