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 1 - 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'****', N'***', 0xE2215FF02C584A4F9252F62E504C171B178AF8B39C758E31BFCE8DC4C35A133A)  
  27. GO  
  28. SET IDENTITY_INSERT [dbo].[UserMaster] OFF  
  29. GO  
NOTE
We save the password hashing & SALT in database.
 
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 2 - 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 3
 
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 4 - 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. [HttpGet]    
  3. public ActionResult Login(string returnURL)    
  4. {    
  5.     var userinfo = new LoginVM();  
  6.     try    
  7.     {    
  8.         // We do not want to use any existing identity information    
  9.         EnsureLoggedOut();   
  10.         // Store the originating URL so we can attach it to a form field    
  11.         userinfo.ReturnURL = returnURL;    
  12.         return View(userinfo);    
  13.     }    
  14.     catch    
  15.     {    
  16.         throw;    
  17.     }    
  18. }    
  19. #endregion 
STEP 5 - 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 6
 
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. //POST: Logout    
  10. [HttpPost]    
  11. [ValidateAntiForgeryToken]    
  12. public ActionResult Logout()    
  13. {    
  14.     try    
  15.     {    
  16.         // First we clean the authentication ticket like always    
  17.         //required NameSpace: using System.Web.Security;    
  18.         FormsAuthentication.SignOut();    
  19.   
  20.         // Second we clear the principal to ensure the user does not retain any authentication    
  21.         //required NameSpace: using System.Security.Principal;    
  22.         HttpContext.User = new GenericPrincipal(new GenericIdentity(string.Empty), null);    
  23.   
  24.         Session.Clear();    
  25.         System.Web.HttpContext.Current.Session.RemoveAll();    
  26.   
  27.         // Last we redirect to a controller/action that requires authentication to ensure a redirect takes place    
  28.         // this clears the Request.IsAuthenticated flag since this triggers a new request    
  29.         return RedirectToLocal();    
  30.     }    
  31.     catch    
  32.     {    
  33.         throw;    
  34.     }    
  35. } 
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 7
 
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. //GET: RedirectToLocal    
  12. private ActionResult RedirectToLocal(string returnURL = "")    
  13. {    
  14.     try    
  15.     {    
  16.         // If the return url starts with a slash "/" we assume it belongs to our site    
  17.         // so we will redirect to this "action"    
  18.         if (!string.IsNullOrWhiteSpace(returnURL) && Url.IsLocalUrl(returnURL))    
  19.             return Redirect(returnURL);    
  20.   
  21.         // If we cannot verify if the url is local to our host we redirect to a default location    
  22.         return RedirectToAction("Index""Dashboard");    
  23.     }    
  24.     catch    
  25.     {    
  26.         throw;    
  27.     }    
  28. } 
STEP 8
 
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. [HttpPost]    
  3. [ValidateAntiForgeryToken]    
  4. public ActionResult Login(LoginVM entity)    
  5. {    
  6.     string OldHASHValue = string.Empty;    
  7.     byte[] SALT = new byte[saltLengthLimit];  
  8.     try    
  9.     {    
  10.         using (db = new DBEntities())    
  11.         {    
  12.             // Ensure we have a valid viewModel to work with    
  13.             if (!ModelState.IsValid)    
  14.                 return View(entity);  
  15.             //Retrive Stored HASH Value From Database According To Username (one unique field)    
  16.             var userInfo = db.UserMasters.Where(s => s.Username == entity.Username.Trim()).FirstOrDefault();  
  17.             //Assign HASH Value    
  18.             if (userInfo != null)    
  19.             {    
  20.                 OldHASHValue = userInfo.HASH;    
  21.                 SALT = userInfo.SALT;    
  22.             }  
  23.             bool isLogin = CompareHashValue(entity.Password, entity.Username, OldHASHValue, SALT);  
  24.             if (isLogin)    
  25.             {    
  26.                 //Login Success    
  27.                 //For Set Authentication in Cookie (Remeber ME Option)    
  28.                 SignInRemember(entity.Username, entity.isRemember);  
  29.                 //Set A Unique ID in session    
  30.                 Session["UserID"] = userInfo.UserID;  
  31.                 // If we got this far, something failed, redisplay form    
  32.                 // return RedirectToAction("Index", "Dashboard");    
  33.                 return RedirectToLocal(entity.ReturnURL);    
  34.             }    
  35.             else    
  36.             {    
  37.                 //Login Fail    
  38.                 TempData["ErrorMSG"] = "Access Denied! Wrong Credential";    
  39.                 return View(entity);    
  40.             }    
  41.         }    
  42.     }    
  43.     catch    
  44.     {    
  45.         throw;    
  46.     }  
  47. }  
  48. #endregion    
  49.  
  50. #region --> Comapare HASH Value    
  51. public static bool CompareHashValue(string password, string username, string OldHASHValue, byte[] SALT)    
  52. {    
  53.     try    
  54.     {    
  55.         string expectedHashString = Get_HASH_SHA512(password, username, SALT);  
  56.         return (OldHASHValue == expectedHashString);    
  57.     }    
  58.     catch    
  59.     {    
  60.         return false;    
  61.     }    
  62. }    
  63. #endregion 
STEP 9
 
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.

BONUS POINT
 
How to create a new hash & salt (which are needed to save in our database) for new user at registration time.

For create SALT & HASH (Which are saved in database) 
  1. //Create a salt  
  2. var salt = Get_SALT();  
  3.   
  4. //Create a hash  
  5. var hash = Get_HASH_SHA512("Your Password""Your UserName", salt);   
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>   


Similar Articles