Authentication is a very important element of developing secure sites. It is the starting point of a secure environment for the protected resources. I have discussed several authentication mechanisms available in ASP.NET and how they join with IIS to provide a secure platform to develop sites in Part I of this article. I will be looking at Form Authentication and how to implement it in several sample web applications.
Form Authentication
Form Authentication is one of the four authentication mechanism available in ASP.NET environment. When using this mechanism the unauthorized user will be directed to a HTML form for authentication. The user will enter their credentials for authentication and credentials can be their user name and password. The authenticated user will be assigned a cookie containing a ticket and on subsequent requests the system will first check for the cookie to see if the user is already authenticated. Using Secure Sockets Layer (SSL) can prevent hackers from steeling the users cookie in communication. The data that travel through the SSL connection is encrypted and less venerable for attacks.
Form Authentication is best suited for following cases
- When you need to contain a private and public area. Public area it available for every one and private area only for the authenticated users.
- When you are not using windows accounts and Active Directories to store usernames and passwords.
- When you need to collect the user credential in login time trough a HTML form
Implementing Form Authentication
Configuring the Internet Information Server (IIS)
You need to configure the IIS to support Form Authentication by selecting Anonymous access in Authentication Methods window in your virtual directory. You can have a look at Part I of this article to see how to access the Authentication Methods window in IIS. Following is a sample picture how it looks
Sample Application 1 (FormAuthentication1.zip project)
Following sample is a very simple application that uses Form Authentication. It involves three files called, Default.aspx, login.aspx and web.config. The user is requesting access to the protected resource Default.aspx. There is only one user have access for the resource call [email protected], with password of password. The username and password are hard-coded into the login.aspx page.
Web.config file
The web.config file should be configured as below and placed at the root directory where Default.aspx page resides.
<configuration>
<system.web>
<authentication mode="Forms">
<forms loginUrl = "login.aspx">
</forms>
</authentication>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</configuration>
Set the authentication mode to Form and login Url attribute to login.aspx. If ASP.NET does not find an authentication cookie with the request, the user will be redirected to the page specified at login Url attribute. By using below syntax, the unauthorized users will be denied access to the directory.
<authorization>
<deny users="?"/>
</authorization>
In this case, user wont be able to access resources in this directory with out any proper From authentication.
Login.aspx file
This is the page that a user will be redirected if their request doesnt contain an authentication ticket. This file name is specified in web.config file. The user will enter their Email address and password and click Login button. If the username and password is equal to the hard-coded information, user will be redirected to the Default.aspx page. Otherwise an error message will be displayed at login.aspx page.
FormsAuthentication.RedirectFromLoginPage(username,createPersistentCookie)
Above method will redirect an authenticated user back to the original requested URL. Username is the name of the user for cookie authentication purposes. createPersistentCookie specifies whether or not a durable cookie should be issued.
//Login button click event
private void btLogin_Click(object sender, System.EventArgs e)
{
//Validate username and password text boxes
if(txUserName.Text == "" || txPassword.Text== "")
{
Msg.Text = "Username or password cannot be empty";
return;
}
//Authenticate the user
bool CookieValue = false;
if ((txUserName.Text== "[email protected]") && (txPassword.Text== "password"))
{
if(Pcooki.Checked)
{
CookieValue = true;
else
{
CookieValue = false;
}
//If valid, redirect to protected resource
FormsAuthentication.RedirectFromLoginPage(txUserName.Text,CookieValue);
}
else
{
//If invalid, display error message
Msg.Text = "Invalid Credentials: Please try again";
txUserName.Text= "";
txPassword.Text= "";
}
}
If the Persistent Cookie check box is selected, the cookie will be valid across browser sessions. Otherwise, the generated cookie will be destroyed when the browser is closed.
Default.aspx file
This is the protected resource. This file simply displays the username of the authenticated user and contains a button to sign-out.
FormsAuthentication.SignOut()
Above method will remove the authentication ticket.
//page Load event
private void Page_Load(Object Src, EventArgs E )
{
//Display the username
Label1.Text = "Hello, " + Context.User.Identity.Name;
}
//Sign-out button click
private void btSignOut_Click(object sender, System.EventArgs e)
{
//Sing-out and redirect to login.aspx page
FormsAuthentication.SignOut();
Response.Redirect("login.aspx");
}
There are many ways that we can store usernames and passwords. We can hard-code them in to an application file like in sample application one. But this will limit the scalability of the application and it is not the most secure place to store the user credentials. The credentials will be human readable and easy to access by unauthorized parties. We can use a text or a XML file to store the user credentials. But again this is human readable and will be easy to identify the credentials by an unauthorized user. If we use a XML file or a text file to store sensitive information, we should consider of using a salted hash for the password or another appropriate symmetric encryption technique to make it less vulnerable to attacks. The following Sample Application 2 demonstrates how to use FormsAuthentication.HashPasswordForStoringInConfigFile method to hash the passwords to store in a XML file.
Given a password and a string password formate, below method produces a hash password suitable for storing in a configuration file. The password is password to hash and password formate is the hash algorithm to use. We can use either "sha1" or "md5.
FormsAuthentication.HashPasswordForStoringInConfigFile(password, password formate)
Sample Application 2 (FormAuthentication2.zip project)
The second application contains a public and a private area. Private area is containing the protected recourses. Every one does have access to the public area. This application contains file in two directory level structures. The root directory and a private directory level ,below the root directory. The usernames and passwords are store in a XML file as protected recourses.
First lets look at the public level root directory and files. The root directory contains a login.aspx file and a web.config file.
Web.config file
The web.config file is configured as below,
<configuration>
<system.web>
<authentication mode="Forms">
<forms loginUrl = "login.aspx">
</forms>
</authentication>
</system.web>
</configuration>
The authentication mode is set to Form and login Url attribute set to login.aspx file. This allows access to all users to the root directory.
Login.aspx file
This is the page that a user will be redirected if their request doesnt contain an authentication ticket. This file name is specified in web.config file and its located at the public area that every one can access. The user will enter their username and password and click Login button. The pass word entered by the user will be hashed by FormsAuthentication.HashPasswordForStoringInConfigFile method before comparing to stored password in XML file.
The password that stored in XML file will be encrypted using above method. This is not the perfect encryption mechanism, because this method is vulnerable to a dictionary attack. User should select non common words with numbers and non alphabetic characters to help prevent dictionary attack. At the same time we can apply Access Control List (ACL) permission to the file, so that only authorized users can read it.
If the user name and password is equal to the XML file details, user will be redirected to the Default.aspx page. Otherwise an error message will be displayed at login.aspx page. The user.xml and Default.aspx pages are located at the private area as protected resources.
The following source code demonstrates the login procedure. First the application will read the user.xml file to a DataSet. Then that information will fill in to a DataTable. The DataTable information will then filtered using Select method to identify whether table contains the input username. If the application match any records, that will be loaded to a DataRow. If not, this user is not a registered user and has to register and try again. For a matched record the input password will hashed and compare against the stored hashed password. If the passwords matched, the user will be redirected to the protected recourses. Otherwise an error message will display at login.aspx page.
private void loginDetails()
{
String filter = "UserName='" + txUserName.Text + "'";
string fileName = "/Details/users.xml";
//Create a DataSet
DataSet loginDS = new DataSet();
bool CookieValue = false;
//Select Persistent Cookie or not
if(pCookie.Checked)
{
CookieValue = true;
}
else
{
CookieValue = false;
}
//Create FileStream to read the users.xml file
FileStream loginFileStream = new
FileStream("C:\\Inetpub\\wwwroot\\FormAuthentication2"
+fileName,FileMode.Open,FileAccess.Read);
//Create StreamReader
StreamReader reader = new StreamReader(loginFileStream);
//Read the xml file to DataSet
loginDS.ReadXml(reader);
loginFileStream.Close();
//Get the data to a DataTable from the DataSet
DataTable usersDetails = loginDS.Tables[0];
//Filter the data to a DataRow from the DataTable
DataRow[] matcheUsers = usersDetails.Select(filter);
//If username is found
if( matcheUsers != null && matcheUsers.Length > 0 )
{
DataRow userRow = matcheUsers[0];
//Encrypt the provided password
string hashedPassword = FormsAuthentication.HashPasswordForStoringInConfigFile(txPassword.Text, "sha1");
//Read password from xml file
string XMLpassword = userRow["UserPassword"].ToString();
//Check if stored password and provided password are equal
if( 0 != String.Compare(XMLpassword, hashedPassword, false) )
{
//If not equals, display error message
Label3.Text = "Invalid Credentials: Please try again";
}
else
{
//If equals, Redirect to protected resource
FormsAuthentication.RedirectFromLoginPage(txUserName.Text, CookieValue);
}
}
else
{
Label3.Text = "You are not a registered user. Please register and try again";
}
}
If the Persistent Cookie check box is selected, the cookie will be valid across browser sessions. Otherwise, the generated cookie will be destroyed when the browser is closed.
The new users can register them self using Register button. The new user will input the username and password and click Register button. The password will be then encrypted using FormsAuthentication.HashPasswordForStoringInConfigFile method before it is saved to the users.xml file. This provide more secure environment than simply keeping the password as human readable text.
private void register()
{
try
{
string fileName = "/Details/users.xml";
//Create a DataSet
DataSet userDS = new DataSet();
//Create new FileStream
FileStream userFileStream = new
FileStream("C:\\Inetpub\\wwwroot\\FormAuthentication2" +fileName,FileMode.Open,FileAccess.Read);
//Create new StreamReader
StreamReader reader = new StreamReader(userFileStream);
//Read the xml file to DataSet
userDS.ReadXml(reader);
userFileStream.Close();
//Encrypt the provided password
string hashedPassword=
FormsAuthentication.HashPasswordForStoringInConfigFile(txPassword.Text, "sha1");
//Create A new DataRow
DataRow newUser = userDS.Tables[0].NewRow();
//Add username to DataRow
newUser["UserName"] = txUserName.Text;
//Add hashed Password to DataRow
newUser["UserPassword"] = hashedPassword;
//Add the new DataRow to DataTable
userDS.Tables[0].Rows.Add(newUser);
//Update the DataSet with new information
userDS.AcceptChanges();
//Create new FileStream
FileStream addUserFileStream = new
FileStream("C:\\Inetpub\\wwwroot\\FormAuthentication2"+fileName, FileMode.Create,FileAccess.Write|FileAccess.Read);
//Create new StreamWriter
StreamWriter writer = new StreamWriter(addUserFileStream);
//Write DataSet data to XML file
userDS.WriteXml(writer);
//Close StreamWriter
writer.Close();
//Close FileStream
addUserFileStream.Close();
Label3.Text = "Your registration was successful, Please login";
}
catch(Exception e)
{
Label3.Text = "Error: " + e.Message.ToString();
}
}
Now lets look at protected level directory and files. Protected level directory contains web.config, user.xml and Default.aspx pages.
Web.config file
The web.config file should configure as below. This will simply deny access for all unauthorized users.
<configuration>
<system.web>
<authorization>
<deny users="?"/>
</authorization>
</system.web >
</configuration>
user.xml file
This contains all the valid usernames and passwords. The passwords are encrypted using FormsAuthentication.HashPasswordForStoringInConfigFile method. As you can see the password is a long string of alphabetical and numeric characters.
<Users>
<Users>
<UserName>gayan</UserName> <UserPassword>134B20FFF068BEBD6014C15D1BF2F3BEC3FDC8A6</UserPassword>
</Users>
</Users>
Default.aspx file
This is the protected resource. This file simply displays the username of the user and contains a button to sign-out.
//page Load event
private void Page_Load(object sender, System.EventArgs e)
{
//Display the username
Label1.Text = "Hello, " + Context.User.Identity.Name;
}
//Sign-out button click
private void btSignOut_Click(object sender, System.EventArgs e)
{
//Sing-out and redirect to login.aspx page
FormsAuthentication.SignOut();
Response.Redirect("login.aspx");
}
At the same time usernames and passwords can be stored in an Access Database or in a SQL Server database. The storing mechanism will be depending on the scalability of your application and number of users you need to support.
Conclusion
I hope above details will help you to understand the ASP.NET Form Authentication mechanism. Its only just a drop of the whole ocean of ASP.NET security.