In this article I am going to show you how to create a secure login screen for your ASP.NET website – the proper way. I have come across many examples which do not show the correct way of implementing this, so I decided to create my own example and clarify the facts a little.
The Web Pages
Let’s start off by creating our web pages. We need three for this example:
- A Home Page (Home.aspx)
- A Login Page (LogIn.aspx)
- A Members-Only Page (Member.aspx)
So create an ASP.NET Web Application Project and add Home.aspx
and LogIn.aspx
to the project. Now in your project tree create a new folder and call it Members. Then create Member.aspx
under the Members folder. Also under the Members folder, add a new Web Configuration File.
Your project tree should now look like this:
The Home Page
Now let’s go to the Home.aspx
and add some code to the page. For the purpose of this example I’m going to keep it simple. Let’s just add a title, a welcome message and a link to the LogIn.aspx
page.
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="MembershipSite._Default" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> </head> <body> <form id="form1" runat="server"> <div> <h2>Home Page</h2> <asp:Label ID="Label1" runat="server" Text="Welcome to my site!"></asp:Label> <br /> <br /> <asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl="~/LogIn.aspx">Access Member's Area</asp:HyperLink> </div> </form> </body> </html>
The Login Page
The LogIn.aspx
page is a little more complicated because we have to add user authentication code here. But first let’s add a Login control to the page, and as before we’ll also add a title and a small message.
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="LogIn.aspx.cs" Inherits="MembershipSite.LogInPage" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title></title> </head> <body> <form id="form1" runat="server"> <div> <h2>LogIn Page</h2> <asp:Label ID="Label1" runat="server" Text="Please log in below to access the membership area."></asp:Label> <br /> <br /> <asp:Login ID="LoginControl" runat="server" onauthenticate="LoginControl_Authenticate"> </asp:Login> </div> </form> </body> </html>
As you can see in the above code we have created an event handler for the Login control called LoginControl_Authenticate
. This is where we will validate the user’s credentials.
For this example we will validate the credentials using a database (the proper way :)). I am going to use SQL Express since that can be automatically installed with Visual Studio.
If you don’t have it installed you can download it for free from Microsoft. You might also want the SQL Server Management Studio Express so that you can edit database schemas and set user permissions, etc.
We will need to create a database – let’s call it MembershipSite. Then create a table called Users with the following schema:
Obviously for a real-world application the Users table will contain much more fields/columns, but for this example the above table is enough.
Next we must implement the code which will authenticate the website users with the Users table. Below is the event handler of the Login control.
protected void LoginControl_Authenticate(object sender, AuthenticateEventArgs e) { bool authenticated = this.ValidateCredentials(LoginControl.UserName, LoginControl.Password); if (authenticated) { FormsAuthentication.RedirectFromLoginPage(LoginControl.UserName, LoginControl.RememberMeSet); } }
As you can see we are calling a method called ValidateCredentials
(which is shown below) to make sure the user exists in the database, and then if the user does exist we are calling FormsAuthentication.RedirectFromLoginPage
. The purpose of this method is to redirect the authenticated user to the members’ only page.
public bool IsAlphaNumeric(string text) { return Regex.IsMatch(text, "^[a-zA-Z0-9]+$"); } private bool ValidateCredentials(string userName, string password) { bool returnValue = false; if (this.IsAlphaNumeric(userName) && userName.Length <= 50 && password.Length <= 50) { SqlConnection conn = null; try { string sql = "select count(*) from users where username = @username and password = @password"; conn = new SqlConnection(ConfigurationManager.ConnectionStrings["MembershipSiteConStr"].ConnectionString); SqlCommand cmd = new SqlCommand(sql, conn); SqlParameter user = new SqlParameter(); user.ParameterName = "@username"; user.Value = userName.Trim(); cmd.Parameters.Add(user); SqlParameter pass = new SqlParameter(); pass.ParameterName = "@password"; pass.Value = Hasher.HashString(password.Trim()); cmd.Parameters.Add(pass); conn.Open(); int count = (int)cmd.ExecuteScalar(); if (count > 0) returnValue = true; } catch (Exception ex) { // Log your error } finally { if (conn != null) conn.Close(); } } else { // Log error - user name not alpha-numeric or // username or password exceed the length limit! } return returnValue; }
The first method in the above code is called IsAlphaNumeric
. It accepts one parameter and returns true
or false
depending on whether the text passed is alphanumeric.
The second method is the ValidateCredentials
method which accepts the user name and password and parameters.
Many login pages are targeted by hackers who want to gain access to your website or maybe even bring it down, so that is why I added that first if
statement to this method. Before trying to authenticate the user against the database we are making sure that the user name and the password do not exceed 50 characters in length and that the user name does not contain any special characters or symbols which could break an SQL query. Obviously, a password should allow for special characters for it to be secure, so we cannot run the password through the IsAlphaNumeric
method.
Next we are building an SQL statement which will count the number of users which have the passed username and password. If none exist the user is not in the system, and if one exists you can authenticate the user. If more than one exists you probably have a problem, but I’m not going to get into how you assign usernames and passwords to new users.
Let’s have a closer look at the SQL query.
string sql = "select count(*) from users where username = @username and password = @password";
As you can see we are not embedding the username and passwords directly into the SQL statement, but instead we are using SQL Parameters (the words with the ‘@’ symbol in front of them.) This makes our system safer because if a hacker tries to perform an SQL Injection, for example, he would not be able to inject any SQL after the quotes in the statement. This is because with SQL Parameters, there are no quotes around string
values in an SQL statement. The quotes will be added internally so thay cannot be manipulated by anyone.
Next we are creating a new SQL Connection, and again, we are doing this the proper way. Instead of hard-coding the connection string, we are getting it from the application’s web.config
file by calling:
conn = new SqlConnection(ConfigurationManager.ConnectionStrings["MembershipSiteConStr"].ConnectionString);
Now at this point there is no connection string in your app.config
file, so let’s create one. Add the following XML to the connectionStrings
section of your app.config
file, and remember to replace the Data Source, the ID, and Password with your values.
<connectionStrings> <add name="MembershipSiteConStr" connectionString="Data Source=DEVMACHINE\SQLEXPRESS;Initial Catalog=MembershipSite;Persist Security Info=True;User ID=your_DB_user_name;Password=your_DB_password" providerName="System.Data.SqlClient" /> </connectionStrings>
Now that that’s done we can move on to the next part of the ValidateCredentials
method, where we are creating the actual SQL Parameters. Note the code we are using when adding the password parameter:
SqlParameter pass = new SqlParameter(); pass.ParameterName = "@password"; pass.Value = Hasher.HashString(password.Trim()); cmd.Parameters.Add(pass);
We are calling Hasher.HashString
for the password. If you are a regular reader of this blog you would have recognised this class from my previous article – How to create your own C# Hashing Class Library. All the HashString
method does is return the hash of the string
passed to it.
But still, why are we hashing the password? Well, because we’re doing it the proper way :). It is never advisable to store passwords as free text in the database. You should either encrypt them or hash them. The advantage of hashing is that a hash is irreversible. So if a hacker gets his hands on your database, with all the user passwords inside, he cannot do anything with it. If he tries to login with one of the passwords, our system will just re-hash the hash, and obviously it won’t find a match in the database. For the hacker to log in he must know the user’s password, and since we’re not storing it anywhere, he cannot get it from us. 🙂
The rest of the code just opens the connection to the database and executes the SQL query. Then according to the result returned we return true
or false
.
The Member Page
Similar to the other two pages we created, let’s add a title and a message, but this time also a Logout link.
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Member.aspx.cs" Inherits="MembershipSite.Members.Member" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> </head> <body> <form id="form1" runat="server"> <div> <h2>Membership Page</h2> <asp:Label ID="Label1" runat="server" Text="Welcome "></asp:Label> <asp:LoginName ID="LoginName" runat="server" /> (<asp:LoginStatus ID="LoginStatus" runat="server" LogoutAction="Redirect" LogoutPageUrl="~/default.aspx" />) </div> </form> </body> </html>
The message we are displaying on screen contains the user’s login name. The system stored the user’s name automatically when he logged in, and we are using the LoginName
control to display it on the page. There is no code involved to show the name, just place the control on your web form and the user name will automatically appear.
We are also using the LoginStatus
control which is a hyperlink which either logs a user in or logs him out. The link text displayed depends on whether the user is logged in or not. It is all automatic, and you do not need to add any code, unless obviously you want to customise some stuff.
This page can only be accessed by authenticated users. If the user is unauthenticated and he tries to access the URL for this page directly, ASP.NET will automatically redirect him to the home page.
But, I hear you ask, how does ASP.NET know which page is my home page and which page is my member’s only page, and how does it know that unauthenticated users must be redirected to the home page? Good question. It doesn’t… yet.
For ASP.NET to correctly handle this logic, we must modify both our web.config
files.
The Members web.config
The web.config under the Members folder must look like this:
<?xml version="1.0"?> <configuration> <appSettings/> <connectionStrings/> <system.web> <authorization> <deny users="?"/> </authorization> </system.web> </configuration>
As you can see there is an XML tag called deny
and its user attribute is set to “?”. This means that all anonymous users are denied access to any files under the Members folder. Therefore if you are unauthenticated and try to access files under the Members folder, you will be redirected.
The Application web.config
Our final task is to let ASP.NET know where to redirect users when they log in and when they are unauthenticated. To do this we must replace the default line <authentication mode="Windows"/>
with the following lines in the main web.config
file:
<authentication mode="Forms"> <forms defaultUrl="~/members/member.aspx" loginUrl="~/login.aspx" slidingExpiration="true" timeout="20"></forms> </authentication>
What we are doing here is switching from Windows authentication to Forms authentication mode, and then we are identifying the url the user is redirected to when he logs in, and also the url the user is redirected to if he is not logged in and tries to access the member only area.
And that’s it. I have shown you the proper way to create a login page and authenticate your users.
I hope you enjoyed this article and found it useful. Please feel free to leave your comments below.
You can download the source code for this ASP.NET application at the end of the article but remember it requires Microsoft Visual Studio 2008, although you can easily copy the code to an older version of Visual Studio.
Stay tuned for more articles soon.
Dave
Great little article, to the point with excellent content, thanks!
Thanks. Glad you found it useful.
Hi Dave, I am glad I founf your article. I am new to progamming and found your article helpful but can you please add more detail on where do I need to add code after creating table.
Great Article!
I’ve been using it since last year and it worked great, well explained and very understandable!
Thanks – I’m happy you found it useful.
Great tutorial !! Found it useful every time I am about to create login pages.
Thank you so much for the above code. However just running into an issue if you could help, part where its pass.Value = Hasher.HashString(password.Trim()); says Hasher does not exist in the current context, can you please point me in the right direction.
Thanks
P
Hi Paul,
As stated in the article the Hasher is a custom built class which returns the hash of a string passed to it. You can read up on how to create this class here – How to create your own C# Hashing Class Library
Dave
Hi Dave,
I created the class as per your link, and compiled to dll. I added that dll as a reference in my project but it still gives the error that Paul has above? VS suggests that I use the full name (here’s that line for me):
pass.Value = LoginLocalTest.HashLibrary.Hasher.HashString(password.Trim());
that makes the error go away. I don’t know why mine is requiring the full path to the dll
Thanks! Much appreciated!
perfect work Dave, you are a creative programmer …
Thanks this was also beneficial because it stressed basic hardening.
Great Tutorial, do you have any idea how you would go about it using VB code behind?
Hi Mary,
It’s been years since I’ve last touhed any VB so I wouldn’t really know the exact syntax, however, the .NET classes should be the same so it’s just a case of changing the syntax. If you look up what classes and methods you need on the MSDN website they usually have examples in both C# and VB.
Cheers,
Dave
Great article and nice to find a tutorial that details the secure way of doing it! I have a query though and must admit I am dabbling in asp.net for my own interest and am no developer! – Once I have created the log in page; how do I then add users to the database? – Would this be a manual task using the Hasher tool and then adding the hashed password to the database, or would I need to find a tutorial for a sign up page to do this?
Any pointers or help would be much appreciated!
Hi Ben,
Within Visual Studio you can create an ASP.NET Web Application and by default this comes with what you are after. As soon as you create a new ASP.NET Web Application, Visual Studio will create the framework for a basic website which also includes pages which handle the user maintenance part. It creates a database in the background (on SQL Express I believe) and allows you to add website users to it through the webpages which have been automatically created for you.
Give that a try – it’s a great starting point for anyone who wants to build a website with a user login section.
Cheers,
Dave
During debug i found that , in the following
int count = (int)cmd.ExecuteScalar();
if (count > 0) returnValue = true;
count remains zero…due to which the return value is false
please help as i am completely new to the .net environment
It’s because the query you are running is not returning anything. I’m assuming this is from the login part – you probably don’t have a user with the credentials you are passing in the database so it’s not finding a match and therefore returning false.
Cheers,
Dave
i have created a database and had entered inputs….it does not even enter the if-part
as count = 0;
The user name and password in the database must be different from what you are using to log in with. That is the only way cmd.ExecuteScalar() will return a zero without giving an exception.
thanks the problem was that i had not stored the hashed value of the password.
Sorry, I’m totally new to this and learning right from the scratch.
Here we give the parameter username and password but the given password is convert hash string value. In db table password value not is not not converted hash string value. How it’s work ?
great article
I was suggested this web site by my cousin.
I am not sure whether this post is written by him as nobody else know such detailed about my trouble.
You’re incredible! Thanks!
why is everything alaway in C#?
where do we go for VB ??
you can go on this link and convert your c# code in VB.
though you will need to double check code which is converted here. because there can be some minor issues. but you can convert your whole c# page here.
http://www.developerfusion.com/tools/convert/csharp-to-vb/
I used this method for my page and it works great.
Thanks
I was suggested this blog by my cousin. I’m not sure whether this post is written by him as no one else know such detailed
about my difficulty. You are amazing! Thanks!
So after I followed this tutorial I’m having two errors I can’t seem to fix. They are Can’t load type WebSite2.Members.Member (My project is called WebSite2 instead of Membership). Do you have any idea why I might be having this error?
If I change it to CodeFile it will load, however, then it says Home.aspx and LogIn.aspx won’t load and takes me to a page that shows the entire website directory.
Hey,
nice tut, but I have one question regarding hashing of passwords.
How can I use that component to hash passwords when they are entered into the DB?
Hi,
Thanks for the great information. The server side is secured now, but the logging in itself is not secured against sniffers (e.g. wireshark). I was looking for possibilities that are safe for password sniffers, i.e. without using SSL. I can’t figure out one. Since all client side encrypting has to be sent over the unsecured connection too, the hacker can always tap that information (JavaScript or alike) too. Does anyone knows a solution.
Kind regards,
Erik Stroeken
I used the above code and find one error that is Error 1 The name ‘Regex’ does not exist in the current context Plz anY0ne here can tell me how to remove this error….Thanks
@masha you might be looking for this:
using System.Text.RegularExpressions;
Nice Article
How would you re-direct a user who is trying to force the members page in the URL back to the Home page?
thanks alot,very nice.
Jux relieved!! Waaaaat! Thank you Dave
I was not able to get this working. I’m hoping someone can help.
I get this error on browser:
==============
Parser Error
Description: An error occurred during the parsing of a resource required to service this request. Please review the following specific parse error details and modify your source file appropriately.
Parser Error Message: Could not load type ‘MembershipSite._Default’.
Source Error:
Line 1:
Line 2:
Line 3:
=================
Here’s what I did/tried:
1) I downloaded your source files and extracted the files & folders to my Win 2008 IIS web server.
2) I used SQL express to create the database “MembershipSite” and “Users” table and I populated that with a few users.
3) I set my server_name, instance_name, SQL username, SQL password in web.config file. (it wasn’t in the app.config file as mentioned in instructions).
Thanks for this article. It seems like an excellent secure design and I hope someone or Dave can help me get it working. I can donate some $ if that helps.
I’m facing problem of session timeout randomly. To overcome the sessions timeout issue; I’m trying to implement the Form Authentication on one of my live project (Asp.Net 4.0 C# and SQL Server), But I’m unable to implement the Form Authentication as there is URL’s routed as explained below.
Below is my current project folder structure,
Folder Structure | Actual URL | Routed URL
Files in Root (Folder)
Dashboard.aspx | ~/dashboard.aspx | dashboard
Login.aspx | ~/login.aspx | login
Product.aspx | ~/dashboard.aspx | dashboard
Files in Root/Website (Folder)
index.aspx | ~/website/index.aspx | index
Signup.aspx | ~/signup.aspx | signup/{type}/{packageId}
Note – All files in the root folder requires authentication and all files under website folder are accessed by all users.
I did a search on google, unfortunately I’m unable to find a video which explains about the Form Authentication implementation on the project where URL Routing is set.
Please advise what will be the best possible solution to overcome the problem stated above.
Many thanks for the great read!
Very good post. I absolutely appreciate this site. Continue
the good work!
it worked great, well explained and very understandable!
Thank You
Really great article, very appreciated. Does anyone has any suggestion on how should be implemented roles for different users to this system?
Thank you,
Martin
Is there a way to redirect to more than 1 particular page depending on who logins in?
Superb site you have here but I was wondering if you knew of any discussion boards that cover the same topics talked about in this article?
I’d really love to be a part of community where I can get suggestios from other knowledgeable individuals that share the
same interest. If you have any recommendations, please let me know.
Thanks!
It is indeed very simple, but is this secure? have you used this way of authentication on a production site?
Dave, great great write up! i know this is probably a dead thread by now, but when attempting to run this, i still am not able to login? i have changed the connection string to my sql database and changed the select statement accordingly. but still no luck.
Any suggestions?
Hey great post! I hope it’s alright that I shared this on my Twitter, if not, no
issues just let me know and I’ll remove it. Regardless keep
up the good work.
thanks for the effort but its really irrelevant to any practical use.
1. You are assuming we want to use asp:login control (my guess- it’s the only way you know how to do it)
2. You don’t restrict the page to any specific role (maybe you don’t know how to programatically assign someone to a role once authenticated- my guess)
3. Everyone goes the easy way out on these and assigns a user, so thanks for not doing that.
4. You are correct about Hashing the password, that is something important everyone should know.
5. Same for SQL Parameters, good point.
Thanks for your willingness to share!
Hi John, thanks for your comments man, but I actually do know how to add users to roles.
I usually use ASP.NET’s membership provider for SQL databases – System.Web.Security.SqlMembershipProvider to handle the website users. I add the required schema with aspnet_regsql.exe -A and in the web.config I add in my membership provider and my role manager provider System.Web.Security.SqlRoleProvider which handles the user roles. The users are tied to roles in the database and the SqlMembershipProvider and SqlRoleProvider handle the heavy lifting for you, but I’m assuming from your comments you already knew all this.
Anyway, the point of this article was not to go into the membership provider side of things, but maybe I should have been more clear in my text about what this actually is. I might even add a separate article on how to use the membership classes.
Thanks for pointing this out.
Dave
Hey Dave,
Thanks so much for this great article. I realize this was published in 2009 so I was wondering if this is still how you would handle creating a secure login for an ASP.Net site today or if there are more modern and more secure ways to handle it now?