Introduction
Let’s first establish what the purpose of code is in the first place.
For this article, the purpose of code is to create Login & Logout Functionality in MVC using Form Authentication. We also discuss about the best way to store the password in database using HASHING.
Background
The most important question is how password are protected. If you storing the password in a plain-text or using encryption/decrypation (2-way) then it is a horrible idea. If you store the password in ecryption format then its 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 Password, One Unique Field (i.e. username or mobile or email) and SALT Key using SHA512 Algorithm (bcrypt/PBKDF2/scrypt are also best alorithms for hashing) and also create a unique SALT Key using CSPRNG. Then we store HASH Value & SALT Key in database.
We dont need to know the password we just verify the inputed password. So, When the user attempts to login, we create a 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 they entered invalid login credentials.
Using the Code
First of all we need to create a database & datatable which contains users` information. HERE We start the CODE.
STEP 01: Create A Database with Name "DemoLoginFunctionality".
Script:
CREATE DATABASE DemoLoginFunctionality;
Below Script Is use to create a Datatable with Data Entries
USE [DemoLoginFunctionality]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[UserMaster](
[UserID] [bigint] IDENTITY(1,1) NOT NULL,
[Username] [nvarchar](50) NOT NULL,
[HASH] [nvarchar](max) NOT NULL,
[SALT] [varbinary](512) NOT NULL,
CONSTRAINT [PK_UserMaster] PRIMARY KEY CLUSTERED
(
[UserID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
SET IDENTITY_INSERT [dbo].[UserMaster] ON
GO
INSERT [dbo].[UserMaster] ([UserID], [Username], [HASH], [SALT]) VALUES (1, N'suchit', N'duD96EJIW3AhCKE9vcl8aev1M2yULlLS4dHqqyFISrpvAdjNvNlVjKHC2Xsi4wsDyHRWa/wxeGmNqd37nQdgS+IhX/AsWEpPklL2LlBMFxsXiviznHWOMb/OjcTDWhM6', 0xE2215FF02C584A4F9252F62E504C171B178AF8B39C758E31BFCE8DC4C35A133A)
GO
SET IDENTITY_INSERT [dbo].[UserMaster] OFF
GO
NOTE: We save the password hashing & salt in database.
--- At the time of Registration (Generate HASH Value & SALT Key) ---
1) First we need to Create A SALT Key. We use CSPRNG (cryptographically secure pseudo-random number generator) for create 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 is uses mathematical formulas to produce sequences of random numbers.
#region --> Generate SALT Key
private static byte[] Get_SALT()
{
return Get_SALT(saltLengthLimit);
}
private static byte[] Get_SALT(int maximumSaltLength)
{
var salt = new byte[maximumSaltLength];
using (var random = new RNGCryptoServiceProvider())
{
random.GetNonZeroBytes(salt);
}
return salt;
}
#endregion
2) Now, second step is to create a HASH Value. we use SHA512 For Create a HASH Value.
What is password hasing?
Hashing performs a one-way transformation on a password, turning the password into another String, called the hashed password. “One-way” means that 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.
hashSHA512("hello") = ekFc4y+0WZ/jHte2PNzvSLC/WLKel86uRb71r6JliSrgjEkKeFQkTL2Ltix+1mcybYsJZuaAp7x7dMUi+1sQCmzR/JoMziQU/XKv0YK92oYMywLb9QJYNYiZDITZNlNu
hashSHA512("hollo") = HntRl+7m3KQ6fDNhqGPYDz3tCAan9gtVL3ad/Z6Qm5aQ3bzDUXUBy+mfgmSVmzfQBYV+3K8A9pWwcRFJxpNYwwVKsB4HudvUsCGPE0JiDx+nX9ot/UU7fn5V1jOTos1g
hashSHA512("helle") = UXaSsPL0G0sY/jqccVMrjazoFgpeDihMu5xwSo3AYWqpHuozHee9zfRviHL1SF+6E/0nU/aV/rM+LLjbTUfBpNI4nPKFwCTmRYmvMrT2npHHsBaIO/w8cpIsJ7y5WiNv
What is SHA512?
The Secure Hash Algorithm (SHA512) is a set of cryptographic hash functions designed by the National Security Agency (NSA).
public static string Get_HASH_SHA512(string password, string username, byte[] salt)
{
try
{
byte[] plainTextBytes = Encoding.UTF8.GetBytes(password + username);
byte[] plainTextWithSaltBytes = new byte[plainTextBytes.Length + salt.Length];
for (int i = 0; i < plainTextBytes.Length; i++)
{
plainTextWithSaltBytes[i] = plainTextBytes[i];
}
for (int i = 0; i < salt.Length; i++)
{
plainTextWithSaltBytes[plainTextBytes.Length + i] = salt[i];
}
HashAlgorithm hash = new SHA512Managed();
byte[] hashBytes = hash.ComputeHash(plainTextWithSaltBytes);
byte[] hashWithSaltBytes = new byte[hashBytes.Length + salt.Length];
for (int i = 0; i < hashBytes.Length; i++)
{
hashWithSaltBytes[i] = hashBytes[i];
}
for (int i = 0; i < salt.Length; i++)
{
hashWithSaltBytes[hashBytes.Length + i] = salt[i];
}
return Convert.ToBase64String(hashWithSaltBytes);
}
catch
{
return string.Empty;
}
}
Now, Save the HASH Value & SALT Key in database.
STEP 02: Create New MVC Application Project.
1) On the File menu, click New Project.
![Image 1](/KB/Articles/1112382/1.png)
2) In the New Project dialog box under Project types, expand Visual C#, and then click Web and In the Name box, type "LoginFunctionalityMVC" then 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 which is stay on center of the right side.
![Image 2](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)
![Image 3](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)
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 found the solution explorer.
![Image 4](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)
2) In solution explorer right click on "Models" Folder. Then Click on the "Add" then click on the "New Item..."
![Image 5](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)
3) Now click on the Visual C# and Select ADO.NET Entity Data Model, Name it "DBModel" and click on Ok
![Image 6](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)
4) Select EF Designer from Database and click on "Next"
![Image 7](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)
5) Now click on new connection, then define "server name", select authentication mode either Windows or SQL Server if SQL server then enter username or password. And finally, select database "DemoLoginFunctionality" under Connect to a Database then click on OK
![Image 8](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)
![Image 9](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)
6) Now Declare a name of connection string as "DBEntities" under Save connection setting in Web.config as: then click on next
![Image 10](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)
7) select version of entity framework. select "Entity Framework 6.x" then click on next
![Image 11](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)
8) Expand "Table" then exand "dbo" and then select our datatable "UserMaster". now, Give the name space as "Models" under Model Namespace: and then click on OK
![Image 12](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)
9) Now Buid Your Project by pressing CLTR + B for updating every entities perfectly.
STEP 04: Add a new empty controller
1) To add a controller. Right click on "controller folder" select "Add" the click on "controller".
2) Now, In Add Scaffold Dialog box select "MVC 5 Controller - Empty" then Click on Add and name it as "HomeController" and click on Add
Create A New ActionResult
method name as 'Login'.
[HttpGet]
public ActionResult Login(string returnURL)
{
var userinfo = new LoginVM();
try
{
EnsureLoggedOut();
userinfo.ReturnURL = returnURL;
return View(userinfo);
}
catch
{
throw;
}
}
public class LoginVM
{
public string Username { get; set; }
public string Password { get; set; }
public string ReturnURL { get; set; }
public bool isRemember { get; set; }
}
STEP 05: Add a View.
1) For this, Right click on your "Login" action method and then select a "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 a Email, Password textbox, Checkbox (for Remember Me Option) & submit button in form tag on Index.cshtml (view)
//Paste below code in your view
@model LoginFunctionalityMVC.Models.LoginVM
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Login</title>
</head>
<body>
@using (Html.BeginForm("Login", "Home", FormMethod.Post))
{
@Html.AntiForgeryToken()
@Html.HiddenFor(s => s.ReturnURL)
<h1>Login Functionality In MVC</h1>
<div>
@if (TempData["ErrorMSG"] != null)
{
<label style="color:maroon;"> @TempData["ErrorMSG"] </label>
<br /><br />
}
@Html.TextBoxFor(s => s.Username, new { @placeholder = "Username" })
<br /> <br />
@Html.PasswordFor(s => s.Password, new { @placeholder = "Password" })
<br /> <br />
@Html.CheckBoxFor(s => s.isRemember) Remember ME
<br /> <br />
<button type="submit">Login</button>
</div>
}
</body>
</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 initialization, at that time we need to check that current session is logout. so fist we logout the existing user.
For this, we create a two methods as "EnsureLoggedOut
" and "Logout
"
private void EnsureLoggedOut()
{
if (Request.IsAuthenticated)
Logout();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Logout()
{
try
{
FormsAuthentication.SignOut();
HttpContext.User = new GenericPrincipal(new GenericIdentity(string.Empty), null);
Session.Clear();
System.Web.HttpContext.Current.Session.RemoveAll();
return RedirectToLocal();
}
catch
{
throw;
}
}
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 a one method as "SignInRemember
" for Set Authentication in Cookie (Remeber ME Option) & one method for redirect to page. name as "RedirectToLocal
"
private void SignInRemember(string userName, bool isPersistent = false)
{
FormsAuthentication.SignOut();
FormsAuthentication.SetAuthCookie(userName, isPersistent);
}
private ActionResult RedirectToLocal(string returnURL = "")
{
try
{
if (!string.IsNullOrWhiteSpace(returnURL) && Url.IsLocalUrl(returnURL))
return Redirect(returnURL);
return RedirectToAction("Index", "Dashboard");
}
catch
{
throw;
}
}
STEP 08: Now, all pre-required methods are done.. Finally we move to create a one method for validate username and password.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginVM entity)
{
string OldHASHValue = string.Empty;
byte[] SALT = new byte[saltLengthLimit];
try
{
using (db = new DBEntities())
{
if (!ModelState.IsValid)
return View(entity);
var userInfo = db.UserMasters.Where(s => s.Username == entity.Username.Trim()).FirstOrDefault();
if (userInfo != null)
{
OldHASHValue = userInfo.HASH;
SALT = userInfo.SALT;
}
bool isLogin = CompareHashValue(entity.Password, entity.Username, OldHASHValue, SALT);
if (isLogin)
{
SignInRemember(entity.Username, entity.isRemember);
Session["UserID"] = userInfo.UserID;
return RedirectToLocal(entity.ReturnURL);
}
else
{
TempData["ErrorMSG"] = "Access Denied! Wrong Credential";
return View(entity);
}
}
}
catch
{
throw;
}
}
public static bool CompareHashValue(string password, string username, string OldHASHValue, byte[] SALT)
{
try
{
string expectedHashString = Get_HASH_SHA512(password, username, SALT);
return (OldHASHValue == expectedHashString);
}
catch
{
return false;
}
}
STEP 09: Now, the last stage is to create a Custom Authorize Attribute In MVC. Because it check that user is login or not before any page initialize.
Create a class name as "CheckAuthorization.cs" in "Models Folder"
public class CheckAuthorization : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (HttpContext.Current.Session["UserID"] == null || !HttpContext.Current.Request.IsAuthenticated)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.StatusCode = 302;
filterContext.HttpContext.Response.End();
}
else
{
filterContext.Result = new RedirectResult(System.Web.Security.FormsAuthentication.LoginUrl + "?ReturnUrl=" +
filterContext.HttpContext.Server.UrlEncode(filterContext.HttpContext.Request.RawUrl));
}
}
else
{
}
}
}
STEP 10: Now, just I 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
<system.web >
<authentication mode = "Forms" >
<forms loginUrl = "~/Home/Login" timeout = "2880" / >
</authentication>
</system.web>