As we discussed last time, the simplest approach to using forms authentication in ASP.NET is to store all information, including usernames and passwords, in the Web.config file. This is a great way to quickly get rudimentary authentication up and running on your site, but it has considerable limitations. In particular:
- You can’t use a registration form to allow new users to add themselves, because you can’t have your program code edit and resave the Web.config file as the site is running.
- Adding users by manually editing the Web.config file is a pain and causes your Web application to restart, which will affect existing visitors.
- Users cannot periodically change their own passwords.
To overcome these limitations, we should stop using Web.config as the repository for the user information and move that information over to a database. We can then change our authentication code to query this database after the user fills out a login form. Here’s what we’ll do, in a nutshell:
- Change Web.config to remove the user credentials that were stored directly in it.
- Change our Login button’s code so that it calls another function to validate the user against a database.
- Write a validateUser method that will do most of the work of figuring out whether the user exists and whether he or she provided the right password.
- Write an addUser method to get new users into the database.
For consistency, we'll use basically the same code listings as we did in the previous article and just make changes where needed. But that doesn't mean you must read the previous article. You can jump right in and start here.
Web.config, a configuration file that all ASP.NET applications typically have in their root folder, can contain both an <authentication> and an <authorization> section. The <authentication> section contains details of how ASP.NET should make a visitor prove who he or she is, while the <authorization> section indicates which authenticated users have access to .aspx files in the folder.
Listing A shows the same Web.config file that we used last time, except with one big difference (which we’ll get to in just a second). The <authentication> element indicates that ASP.NET should use the Forms method of authentication, as opposed to the integrated Windows authentication or Microsoft’s Passport authentication. The <forms> element then goes on to give more specifics about how ASP.NET should use forms authentication, including indicating where the login page is located. In the previous article, we also had a <credentials> element so we could store usernames and passwords right there in the Web.config file. This time around, we’ve left the <credentials> section in there for reference but enclosed it in XML comment tags so that it won't be used by ASP.NET. That’s the one big difference.
Finally, the <authorization> element, with the <deny users="?"/> child element, indicates that no anonymous users will be allowed to see .aspx pages. Everybody will be forced to authenticate, which means they will all be sent to the login page at least once per session.
Listing B contains a click event handler for a button on a login form. Again, we’ve left all the code in there from last time, but we placed comment markers in front of the code we don’t need and clearly indicated which code we added this time.
As the listing shows, we can no longer simply have the click event call the convenient Authenticate method of the FormsAuthentication class. That method looks up names and passwords in the Web.config file and compares them with the parameters that are passed to it. But since we are using a database to store names and passwords, we’ll write our own method, validateUser, which we'll look at next. The button’s click event will call our validateUser method and pass it the username and password the user provided in the text fields on the Web form. If validation succeeds, RedirectFromLoginPage is called so that the user is redirected to whichever page he or she was originally trying to access before being asked to log in.
The validateUser method
Listing C contains the validateUser method, which does the following:
- It accepts the username and password as string parameters.
- It "hashes" the password using a simple method provided by .NET, because the passwords in the database are also hashed so that they do not appear in clear text.
- It connects to a database and runs a query looking for the username in a table that holds usernames and passwords.
- If it finds the username, it compares the password stored in the database with the hashed version of the password that was a parameter to the method.
The database code in the listing is fairly straightforward. Since this isn’t an article about. NET data access, we won’t go into any detail. However, the hashing of the password, which occurs before the database code, deserves special mention.
If you're going to store usernames and passwords in a database, you might consider transforming the password so that it does not appear in plain text in a database field, which makes it easy to steal. .NET provides a simple method to transform a password into a fairly meaningless series of characters. The method is HashPasswordForStoringInConfigFile and it is also part of the FormsAuthentication class. Don’t let the “ConfigFile” portion of that big method name put you off; the method simply transforms the text that is passed to it, and then you can store the result anywhere you want.
Here is an example of what the hashing method does. If you supply this C# code:
string hashed =
the hashed variable will contain this string:
The downside to hashing the passwords is that there is no way (to my knowledge) to help users who forget their passwords. In other words, you can't unhash it to figure out what it looks like in clear text. Instead, you can assign a new temporary password for the user, and then he or she can log on and change it if your site includes that capability.
We can now discuss the code for adding users to your site. We could not do this in the last article because of the limitations of storing names and passwords directly in Web.config. Now that we’ve switched to using a database, we can write a simple addUser method that accepts a username and password, hashes the password, and inserts a new row into a database table. Listing D shows an example of such a method.
As you can see, the code is similar to that of the validateUser method: The username and a clear-text password are passed in, the password is hashed, and that hashed version is used in the database code. The big difference is that the database code involves a SQL INSERT instead of SELECT, which also means that no DataReader class object is required.
We call the addUser method from the click event of a button that submits a registration form, such as the simple form in Figure A.
Room for improvement
Our listings work as starting points, but you should consider some of the additions you'll probably want to make. For example, what should the login button’s click event handler do if validateUser fails? Our simple example added a failure message to the controls on the page. But what if the user tries and fails over and over again? Is the person an unauthorized user trying to guess passwords? Should you keep track of these validateUser failures and lock the account after a few failed attempts?
Also, consider what addUser should do if the username it tries to add already exists in the database. Our sample code doesn’t take this possibility into account, but your real-world code will have to.
Finally, despite the security improvements we've introduced in this article, it's important to understand the dangers that remain. We’ve turned to hashing passwords and storing them in a database rather than in a clear-text configuration file, which is a good step forward in security. But it's essential to remember that all of our hashing is occurring in ASP.NET server code after the password arrives from its journey across the wire from the user’s browser. Unless the browser is connected to your server via SSL, that password is sent over the wire in clear text. If the security of the user’s password is a top priority, you should require SSL connections to your site.
Increased functionality for little effort
By switching to database storage of user information, we’ve added considerably more functionality to our site authentication system compared to the Web.config-based solution presented in the previous article. Our new approach certainly requires more code, but it’s not difficult code. The extra security step of hashing passwords before saving them is achieved with just one method call, and most of the other code additions are for standard database access—a coding topic that is already familiar to many of us. The ratio of added functionality to expended effort is high indeed. Take the next step: Get that user information out of Web.config!