Securing Your Logins With ASP.NET MVC





3.00/5 (5 votes)
Securing Your Logins With ASP.NET MVC
- Part 1 – Securing Your Logins With ASP.NET MVC (this post)
- Part 2 – Securing Web.API Requests With JSON Web Tokens
An architectural pattern that is becoming more popular is using ASP.NET MVC with a Web.API
layer servicing the web front end via angular.js or similar technology. A kind of hybrid SPA with all the benefits that ASP.NET brings to the table.
This is a two part primer running through what I do to secure logins to MVC applications. In part two, I will expand on this post to cover how to secure the Web.API
layer utilizing the security built into ASP.NET.
If you ever go to a website and you cannot remember your password, you will most likely have requested a password reminder. If you get sent your current password in plain text, then that is bad news. It means the website is storing passwords in plain text and if they get hacked, then they will have access to those passwords, and knowing the fact that people have a tendency to use the same password on multiple sites, then they could compromise multiple sites that you use. It is really important to salt and hash your passwords for storage in the database. By doing this, you can do a string comparison against the hash and not the actual password. Here, I will go through the process in code.
As usual, you will have a login screen asking for username (or email address) and password. I won’t go into the MVC/Razor side here, just the important code.
Take in the two form values:
private readonly ISecurityService _securityService;
private User LoggedInUser { get; set; }
private bool ValidUser { get; set; }
[HttpPost]
public ActionResult Login(FormCollection formCollection)
{
var email = formCollection["Email"].ToString();
var password = formCollection["Password"].ToString();
CheckValidUser(email, password);
if (ValidUser)
{
return RedirectToAction("Index", "User");
}
return RedirectToAction("InvalidLogin", "Error");
}
public void CheckValidUser(string eMailAddress, string password)
{
User user = _securityService.LookupUser(eMailAddress, password);
if (user != null)
{
ValidUser = true;
LoggedInUser = user;
Session["userFullName"] = user.FirstName + " " + user.LastName;
}
else
{
ValidUser = false;
}
}
The LookupUser
method on the SecurityService
is where the magic happens:
public class SecurityService : ISecurityService, IDisposable
{
private UnitOfWork _unitOfWork;
public User LookupUser(string eMailAddress, string password)
{
_unitOfWork = new UnitOfWork();
User user = null;
User firstOrDefault = _unitOfWork.UserRepository.GetUser(eMailAddress);
if (firstOrDefault != null)
{
var saltForUser = firstOrDefault.PasswordSalt;
var passwordAndSalt = password + saltForUser;
var hash = PasswordHash.GetPasswordHashAndSalt(passwordAndSalt);
var validUser = _unitOfWork.UserRepository.GetUser(eMailAddress, hash);
if (validUser != null)
{
// correct password match
user = validUser;
}
}
return user;
}
public void Dispose()
{
}
}
This method looks up the User from the database via a UserRepository
and appends the salt to the password the user has provided. I explain what salts and hashes are a little later on, but for now know they are just a random string representation of a passkey. This combination of password and salt are then passed into the GetPasswordHashAndSalt
method of the PasswordHash
class.
public class PasswordHash
{
public static string CreateSalt(int size)
{
var cryptoServiceProvider = new RNGCryptoServiceProvider();
var buffer = new byte[size];
cryptoServiceProvider.GetBytes(buffer);
return Convert.ToBase64String(buffer);
}
public static byte[] GetByteArray(string inputString)
{
return Encoding.UTF8.GetBytes(inputString);
}
public static string GetPasswordHashAndSalt(string message)
{
SHA256 sha = new SHA256CryptoServiceProvider();
byte[] dataBytes = GetByteArray(message);
byte[] resultBytes = sha.ComputeHash(dataBytes);
return GetString(resultBytes);
}
private static string GetString(byte[] resultBytes)
{
return Convert.ToBase64String(resultBytes);
}
}
The GetPasswordHashAndSalt
method reads the string
into a byte array and encrypts it using SHA256, then returns a string
representation of it back to the calling method. This is then the hash of the salted password which should be equal to the value in the database. On line xx, the repository does another database look-up to get the User that matches both the email address and hash value. OK, so how do we get those hashes and salts in the database in the first place? When a new user account is set up, you need to generate a random salt like this:
const int saltSize = 4;
var salt = PasswordHash.CreateSalt(saltSize);
string message = password + salt;
var hashAndSalt = PasswordHash.GetPasswordHashAndSalt(message);
You then store the usual user details in the database along with the salt and the hashAndSalt
values in place of the password. By generating a new salt each time an account is created, you minimize the risk that a hacker will get the salt and regenerate the passwords from the hashAndSalt
value.
Now back to the login POST
method on the controller. Once the user has been authenticated in the system, you need to create a cookie for the ASP.NET forms authentication to work.
First, create a ticket that stores information such as the user logged in.
var ticket = new FormsAuthenticationTicket(1, LoggedInUser.Username,
DateTime.Now, DateTime.Now.AddMinutes(30), true, FormsAuthentication.FormsCookiePath);
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
cookie.HttpOnly = true;
Response.Cookies.Add(cookie);
Where LoggedInUser
is the valid User
object we got from the database earlier. To check for a valid ticket throughout the site, you can decorate each action method with [Authorize]
filter attributes, or you could do the whole site and just have [AllowAnonymous]
attributes on the login controller actions. To do this for the whole site, firstly add a new AuthorizeAttribute
to the FilterConfig.cs file inside App_Start
like this:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new AuthorizeAttribute());
}
}
Then in the Application_AuthenticateRequest
method to the global.asax.cs file add this:
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
//Extract the forms authentication cookie
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
// If caching roles in userData field then extract
string[] roles = authTicket.UserData.Split(new[] { '|' });
// Create the IIdentity instance
IIdentity id = new FormsIdentity(authTicket);
// Create the IPrinciple instance
IPrincipal principal = new GenericPrincipal(id, roles);
// Set the context user
Context.User = principal;
}
}
This method will check every request coming in to see if it has a valid FormsAuthentication
ticket. If it doesn’t, then it will redirect the user to the default location specified in the web.config file.
<system.web>
<authentication mode="Forms">
<forms name=".ASPXAUTH" loginUrl="http://localhost:12345/"
protection="All" cookieless="UseCookies" timeout="30" />
</authentication>
<compilation debug="true" targetFramework="4.5.1" />
<httpRuntime targetFramework="4.5" maxUrlLength="2000" />
</system.web>
The post Securing Your Logins With ASP.Net MVC appeared first on Don't Believe The Type.