Custom Login Functionality in MVC

In this article, the purpose of the code is to create Login and Logout Functionality in MVC, using Form Authentication. We will discuss about the best way to store the password in the database using HASHING too.
 
So, here we go.
 
The most important question is how passwords are protected. If you are storing the password in a plain-text or using encryption/decryption (2-way), then it is a horrible idea. If you store the password in encryption format, then also there is a possibility to revert to the pain-text value using encrypted output.
 
Here is the best solution for storing the password in database. We encrypt the password using one-way hashing algorithms.
First of all, we create a HASH Value of combination of Passwords, One Unique Field (username, or mobile, or email) and SALT Key using SHA512 Algorithm (bcrypt/PBKDF2/scrypt are also best algorithms for hashing). Also create a unique SALT Key using CSPRNG. Then, we store HASH Value & SALT Key in database.
 
We don't need to know the password but we just verify the entered password. So, when the user attempts to login, we create one HASH Value of password and one unique field (which is entered by user) and SALT. Then, it is checked against the hash of their real password which are retrieved from the database. If the hashes match, the user is granted access. If not, the user is told that they have entered invalid login credentials.
 
First of all, we need to create a database & data table which contains users` information. Here, we start the code.
 
STEP 01: Create A Database with Name "DemoLoginFunctionality"
  1. CREATE DATABASE DemoLoginFunctionality;  
The following script is used to create a Datatable with Data Entries.
  1. USE [DemoLoginFunctionality]  
  2. GO  
  3. SET ANSI_NULLS ON  
  4. GO  
  5. SET QUOTED_IDENTIFIER ON  
  6. GO  
  7. SET ANSI_PADDING ON  
  8. GO  
  9. CREATE TABLE [dbo].[UserMaster](  
  10.     [UserID] [bigint] IDENTITY(1,1) NOT NULL,  
  11.     [Username] [nvarchar](50) NOT NULL,  
  12.     [HASH] [nvarchar](maxNOT NULL,  
  13.     [SALT] [varbinary](512) NOT NULL,  
  14.  CONSTRAINT [PK_UserMaster] PRIMARY KEY CLUSTERED   
  15. (  
  16. [UserID] ASC  
  17. )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ONON [PRIMARY]  
  18. ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]  
  19.   
  20. GO  
  21. SET ANSI_PADDING OFF  
  22. GO  
  23. SET IDENTITY_INSERT [dbo].[UserMaster] ON   
  24.   
  25. GO  
  26. INSERT [dbo].[UserMaster] ([UserID], [Username], [HASH], [SALT]) VALUES (1, N'suchit', N'duD96EJIW3AhCKE9vcl8aev1M2yULlLS4dHqqyFISrpvAdjNvNlVjKHC2Xsi4wsDyHRWa/wxeGmNqd37nQdgS+IhX/AsWEpPklL2LlBMFxsXiviznHWOMb/OjcTDWhM6', 0xE2215FF02C584A4F9252F62E504C171B178AF8B39C758E31BFCE8DC4C35A133A)  
  27. GO  
  28. SET IDENTITY_INSERT [dbo].[UserMaster] OFF  
  29. GO  
NOTE: We save the password hashing & SALT in database.
 
1) First, we need to create a SALT Key. We use CSPRNG (cryptographically secure pseudo-random number generator) for creating a SALT Key.
 
What is SALT key?
 
A SALT is random data that is used as an additional input to a one-way function that "hashes" a password.
 
What is CSPRNG?
 
A cryptographically secure pseudo-random number generator (CSPRNG) is a pseudo-random number generator (PRNG) with properties that make it suitable for use in cryptography. It uses mathematical formulas to produce sequences of random numbers.
  1. #region --> Generate SALT Key  
  2.   
  3. private static byte[] Get_SALT()  
  4. {  
  5.     return Get_SALT(saltLengthLimit);  
  6. }  
  7.   
  8. private static byte[] Get_SALT(int maximumSaltLength)  
  9. {  
  10.     var salt = new byte[maximumSaltLength];  
  11.   
  12.     //Require NameSpace: using System.Security.Cryptography;  
  13.     using (var random = new RNGCryptoServiceProvider())  
  14.     {  
  15.         random.GetNonZeroBytes(salt);  
  16.     }  
  17.   
  18.     return salt;  
  19. }  
  20.  
  21. #endregion  
2) Now, the second step is to create a HASH Value. We use SHA512 for that.
 
What is Password Hashing?
 
Hashing performs a one-way transformation on a password, turning the password into another String, called the hashed password. “One-way” means it is practically impossible to go the other way to turn the hashed password back into the original password. They also have the property that if the input changes by even a tiny bit, the resulting hash is completely different.
 
i.e.
 
 
What is SHA512?
 
The Secure Hash Algorithm (SHA512) is a set of cryptographic hash functions designed by the National Security Agency (NSA).
  1. #region --> Generate HASH Using SHA512  
  2.        public static string Get_HASH_SHA512(string password, string username, byte[] salt)  
  3.        {  
  4.            try  
  5.            {  
  6.                //required NameSpace: using System.Text;  
  7.                //Plain Text in Byte  
  8.                byte[] plainTextBytes = Encoding.UTF8.GetBytes(password + username);  
  9.   
  10.                //Plain Text + SALT Key in Byte  
  11.                byte[] plainTextWithSaltBytes = new byte[plainTextBytes.Length + salt.Length];  
  12.   
  13.                for (int i = 0; i < plainTextBytes.Length; i++)  
  14.                {  
  15.                    plainTextWithSaltBytes[i] = plainTextBytes[i];  
  16.                }  
  17.   
  18.                for (int i = 0; i < salt.Length; i++)  
  19.                {  
  20.                    plainTextWithSaltBytes[plainTextBytes.Length + i] = salt[i];  
  21.                }  
  22.   
  23.                HashAlgorithm hash = new SHA512Managed();  
  24.                byte[] hashBytes = hash.ComputeHash(plainTextWithSaltBytes);  
  25.                byte[] hashWithSaltBytes = new byte[hashBytes.Length + salt.Length];  
  26.   
  27.                for (int i = 0; i < hashBytes.Length; i++)  
  28.                {  
  29.                    hashWithSaltBytes[i] = hashBytes[i];  
  30.                }  
  31.   
  32.                for (int i = 0; i < salt.Length; i++)  
  33.                {  
  34.                    hashWithSaltBytes[hashBytes.Length + i] = salt[i];  
  35.                }  
  36.   
  37.                return Convert.ToBase64String(hashWithSaltBytes);  
  38.            }  
  39.            catch  
  40.            {  
  41.                return string.Empty;  
  42.            }  
  43.        }  
  44.        #endregion  
Now, save the HASH Value & SALT Key in database.
 
STEP 02: Create New MVC Application Project.
 
1) On the File menu, click New Project.
 
 
2) In the New Project dialog box under Project types, expand Visual C#, and then click Web. In the Name box, type "LoginLogout" and click on OK.
 
 
3) Now, in the dialog box, click on the "MVC" under the ASP.NET 4.5.2 Templates. Then, click on Change Authentication in the center of the right side.
 
 
 
 
 
STEP 03: So, here is the new new MVC Application created. Now, we need to create an EDMX & bind our database "DemoLogin" with EDMX.
 
1) On the right side, you can find the Solution Explorer.
 
 
 
2) In Solution Explorer, right click on "Models" folder. Then, click on the "Add". Now, click on the "New Item..."
 
 
 
3) Now, click on the Visual C# and select ADO.NET Entity Data Model. Name it "DBModel" and click on OK.
 
 
 
4) Select EF Designer from Database and click on "Next".
 
 
 
5) Now, click on new connection. Define "server name" and select authentication mode to either Windows or SQL Server. If you select SQL server, then enter username or password. Finally, select database "DemoLogin" under Connect to a Database. Click on OK.
 
 
 
 
6) Now, declare a name of connection string as "DBEntities" under Save connection setting in Web.config. Then, click on Next.
 
 
 
7) Select the version of Entity Framework. Select "Entity Framework 6.x"  and click on Next.
 
 
 
8) Expand the "Tables", then expand "dbo" and select your datatable "Users". Now, give the name space as "Models" under Model Namespace. Then, click on OK.
 
 
 
9) Now, build your Project by pressing CLTR + B for updating every entity perfectly.
 
STEP 04: Add a new empty controller
 
1) To add a Controller, right click on "Controllers" folder and select "Add". Then, click on "Controller".
 
2) Now, in Add Scaffold Dialog box, select "MVC 5 Controller - Empty". Click on Add, and name it as "HomeController". Click on Add.
Create a new ActionResult method named as 'Login'.
  1. #region --> Login GET Method   
  2.   
  3.         [HttpGet]  
  4.         public ActionResult Login(string returnURL)  
  5.         {  
  6.             var userinfo = new LoginVM();  
  7.   
  8.             try  
  9.             {  
  10.                 // We do not want to use any existing identity information  
  11.                 EnsureLoggedOut();  
  12.   
  13.                 // Store the originating URL so we can attach it to a form field  
  14.                 userinfo.ReturnURL = returnURL;  
  15.   
  16.                 return View(userinfo);  
  17.             }  
  18.             catch  
  19.             {  
  20.                 throw;  
  21.             }  
  22.         }  
  23.  
  24.         #endregion  
STEP 05: Add a View
 
1) For this, right click on your "Login" action method and then select  "Add View".
 
2) Now, uncheck the "Use a layout page" and click on Add button.
 
3) Add the Model Class in your top of the View.
 
@model LoginFunctionalityMVC.Models.LoginVM
 
4) Add an Email, Password textbox, Checkbox (for Remember Me Option), and a Submit button in form tag on Index.cshtml (View).
  1. //Paste below code in your view  
  2. @model LoginFunctionalityMVC.Models.LoginVM  
  3.   
  4. @{  
  5.     Layout = null;  
  6. }  
  7.   
  8. <!DOCTYPE html>  
  9.   
  10. <html>  
  11. <head>  
  12.     <meta name="viewport" content="width=device-width" />  
  13.     <title>Login</title>  
  14. </head>  
  15. <body>  
  16.     @using (Html.BeginForm("Login""Home", FormMethod.Post))  
  17.     {  
  18.         @Html.AntiForgeryToken()  
  19.         @Html.HiddenFor(s => s.ReturnURL)  
  20.   
  21.         <h1>Login Functionality In MVC</h1>  
  22.         <div>  
  23.   
  24.             @if (TempData["ErrorMSG"] != null)  
  25.             {  
  26.                 <label style="color:maroon;"> @TempData["ErrorMSG"] </label>  
  27.                 <br /><br />  
  28.             }  
  29.   
  30.             @Html.TextBoxFor(s => s.Username, new { @placeholder = "Username" })  
  31.             <br /> <br />  
  32.             @Html.PasswordFor(s => s.Password, new { @placeholder = "Password" })  
  33.             <br /> <br />  
  34.             @Html.CheckBoxFor(s => s.isRemember) Remember ME  
  35.             <br /> <br />  
  36.             <button type="submit">Login</button>  
  37.         </div>  
  38.     }  
  39. </body>  
  40. </html>  
NOTE: Don`t forgot to declare AntiForgeryToken in your View.
 
AntiForgeryToken: The anti-forgery token can be used to help protect your application against cross-site request forgery.
 
STEP 06: When login page initializes, we need to check that current session is logged out. So, first we logout the existing user.
For this, we create two methods, "EnsureLoggedOut" and "Logout".
  1. //GET: EnsureLoggedOut  
  2.        private void EnsureLoggedOut()  
  3.        {  
  4.            // If the request is (still) marked as authenticated we send the user to the logout action  
  5.            if (Request.IsAuthenticated)  
  6.                Logout();  
  7.        }  
  8.   
  9.   
  10.   
  11.  //POST: Logout  
  12.        [HttpPost]  
  13.        [ValidateAntiForgeryToken]  
  14.        public ActionResult Logout()  
  15.        {  
  16.            try  
  17.            {  
  18.                // First we clean the authentication ticket like always  
  19.                //required NameSpace: using System.Web.Security;  
  20.                FormsAuthentication.SignOut();  
  21.   
  22.                // Second we clear the principal to ensure the user does not retain any authentication  
  23.                //required NameSpace: using System.Security.Principal;  
  24.                HttpContext.User = new GenericPrincipal(new GenericIdentity(string.Empty), null);  
  25.   
  26.                Session.Clear();  
  27.                System.Web.HttpContext.Current.Session.RemoveAll();  
  28.   
  29.                // Last we redirect to a controller/action that requires authentication to ensure a redirect takes place  
  30.                // this clears the Request.IsAuthenticated flag since this triggers a new request  
  31.                return RedirectToLocal();  
  32.            }  
  33.            catch  
  34.            {  
  35.                throw;  
  36.            }  
  37.        }  
NOTE: Don`t forgot to declare ValidateAntiForgeryToken in your top of POST Method.
 
ValidateAntiForgeryToken: The feature doesn't prevent any other type of data forgery or tampering based attacks. To use it, decorate the action method or controller with the ValidateAntiForgeryToken attribute and place a call to @Html.AntiForgeryToken(), in the forms posting to the method.
 
STEP 07: We create one method as "SignInRemember" for setting authentication in cookie (Remember Me option) and one method for redirecting to page, named  "RedirectToLocal"
  1.   //GET: SignInAsync     
  2.         private void SignInRemember(string userName, bool isPersistent = false)  
  3.         {  
  4.             // Clear any lingering authencation data  
  5.             FormsAuthentication.SignOut();  
  6.   
  7.             // Write the authentication cookie  
  8.             FormsAuthentication.SetAuthCookie(userName, isPersistent);  
  9.         }  
  10.   
  11.   
  12.   
  13. //GET: RedirectToLocal  
  14.         private ActionResult RedirectToLocal(string returnURL = "")  
  15.         {  
  16.             try  
  17.             {  
  18.                 // If the return url starts with a slash "/" we assume it belongs to our site  
  19.                 // so we will redirect to this "action"  
  20.                 if (!string.IsNullOrWhiteSpace(returnURL) && Url.IsLocalUrl(returnURL))  
  21.                     return Redirect(returnURL);  
  22.   
  23.                 // If we cannot verify if the url is local to our host we redirect to a default location  
  24.                 return RedirectToAction("Index""Dashboard");  
  25.             }  
  26.             catch  
  27.             {  
  28.                 throw;  
  29.             }  
  30.         }  
STEP 08: Now, all pre-required Methods are done. Finally, we move to create one method for validating username and password.
  1. #region --> Login POST Method  
  2.   
  3.         [HttpPost]  
  4.         [ValidateAntiForgeryToken]  
  5.         public ActionResult Login(LoginVM entity)  
  6.         {  
  7.             string OldHASHValue = string.Empty;  
  8.             byte[] SALT = new byte[saltLengthLimit];  
  9.   
  10.             try  
  11.             {  
  12.                 using (db = new DBEntities())  
  13.                 {  
  14.                     // Ensure we have a valid viewModel to work with  
  15.                     if (!ModelState.IsValid)  
  16.                         return View(entity);  
  17.   
  18.                     //Retrive Stored HASH Value From Database According To Username (one unique field)  
  19.                     var userInfo = db.UserMasters.Where(s => s.Username == entity.Username.Trim()).FirstOrDefault();  
  20.   
  21.                     //Assign HASH Value  
  22.                     if (userInfo != null)  
  23.                     {  
  24.                         OldHASHValue = userInfo.HASH;  
  25.                         SALT = userInfo.SALT;  
  26.                     }  
  27.   
  28.                     bool isLogin = CompareHashValue(entity.Password, entity.Username, OldHASHValue, SALT);  
  29.   
  30.                     if (isLogin)  
  31.                     {  
  32.                         //Login Success  
  33.                         //For Set Authentication in Cookie (Remeber ME Option)  
  34.                         SignInRemember(entity.Username, entity.isRemember);  
  35.   
  36.                         //Set A Unique ID in session  
  37.                         Session["UserID"] = userInfo.UserID;  
  38.   
  39.                         // If we got this far, something failed, redisplay form  
  40.                         // return RedirectToAction("Index", "Dashboard");  
  41.                         return RedirectToLocal(entity.ReturnURL);  
  42.                     }  
  43.                     else  
  44.                     {  
  45.                         //Login Fail  
  46.                         TempData["ErrorMSG"] = "Access Denied! Wrong Credential";  
  47.                         return View(entity);  
  48.                     }  
  49.                 }  
  50.             }  
  51.             catch  
  52.             {  
  53.                 throw;  
  54.             }  
  55.   
  56.         }  
  57.  
  58.         #endregion  
  59.  
  60.  
  61. #region --> Comapare HASH Value  
  62.         public static bool CompareHashValue(string password, string username, string OldHASHValue, byte[] SALT)  
  63.         {  
  64.             try  
  65.             {  
  66.                 string expectedHashString = Get_HASH_SHA512(password, username, SALT);  
  67.   
  68.                 return (OldHASHValue == expectedHashString);  
  69.             }  
  70.             catch  
  71.             {  
  72.                 return false;  
  73.             }  
  74.         }  
  75.         #endregion  
STEP 09: Now, the last stage is to create a custom Authorize Attribute In MVC. Because it checks if user is logged in or not before any page initializes.
 
Create a class named "CheckAuthorization.cs" in "Models" folder.
  1. public class CheckAuthorization : AuthorizeAttribute  
  2.    {  
  3.        public override void OnAuthorization(AuthorizationContext filterContext)  
  4.        {  
  5.            if (HttpContext.Current.Session["UserID"] == null || !HttpContext.Current.Request.IsAuthenticated)  
  6.            {  
  7.                if (filterContext.HttpContext.Request.IsAjaxRequest())  
  8.                {  
  9.                    filterContext.HttpContext.Response.StatusCode = 302; //Found Redirection to another page. Here- login page. Check Layout ajaxError() script.  
  10.                    filterContext.HttpContext.Response.End();  
  11.                }  
  12.                else  
  13.                {  
  14.                    filterContext.Result = new RedirectResult(System.Web.Security.FormsAuthentication.LoginUrl + "?ReturnUrl=" +  
  15.                         filterContext.HttpContext.Server.UrlEncode(filterContext.HttpContext.Request.RawUrl));  
  16.                }  
  17.            }  
  18.            else  
  19.            {  
  20.   
  21.                //Code HERE for page level authorization  
  22.   
  23.            }  
  24.        }  
  25.    }  
STEP 10: Now, I just have to put [CheckAuthorization] attribute on top of my controller to access my CheckAuthorization Function which is Custom Authorize Attribute.

STEP 11: Add authentication mode to your web.config.
 
Add the following code to your web.confing.
  1. <system.web >    
  2.     <authentication mode = "Forms" >    
  3.     <forms loginUrl = "~/Home/Login" timeout = "2880" / > //Declare Your Return URL Here. Mean If Login Fail Then Page Redirect This URL    
  4.     </authentication>  
  5. </system.web>