Special Reports
Drill Down on Forms Authentication
ASP.NET 2.0 provides a robust set of functionality for implementing rich and flexible forms authentication in your applications.
Taking advantage of forms authentication requires that you have your own authentication infrastructure with a custom login page that validates a user name and password against a custom store such as your own database. This infrastructure then establishes the security context on each request again (in many cases such systems work based on cookies).
Fortunately, ASP.NET includes a complete infrastructure for implementing such systems. ASP.NET handles the cookies and establishes the security context on each request for you. This infrastructure is called forms authentication, and you'll learn how it works in this chapter.
Forms authentication is a ticket-based (also called token-based) system. This means users who log in receive a ticket with basic user information. This information is stored in an encrypted cookie that's attached to the response, so it's automatically submitted on each subsequent request.
When a user requests an ASP.NET page that isn't available for anonymous users, the ASP.NET runtime verifies whether the forms authentication ticket is available. If it's not available, ASP.NET redirects the user to a login page automatically. At that point, it's your turn. You have to create the login page and validate the credentials within this page. If the user is validated successfully, you tell the ASP.NET infrastructure about the success (by calling a method of the FormsAuthentication class), and the runtime sets the authentication cookie (which contains the ticket) automatically and redirects the user to the page requested originally. With this request, the runtime detects that the authentication cookie with the ticket is available and grants access to the page.
You implement forms authentication entirely within ASP.NET, so you have complete control over how authentication is performed. You don't need to rely on any external systems, as you do with Windows or Passport authentication. You can also customize the behavior of forms authentication to suit your needs. You have the same degree of control over the appearance of forms authentication as you do over its functionality. In other words, you can format the login form in any way you like. This flexibility in appearance isn't available in the other authentication methods. Windows authentication needs the browser to collect credentials, and Passport authentication requires that users leave your Web site and visit the Passport site to enter their credentials.
Forms authentication uses standard HTML as its user interface, so all browsers can handle it. Because you can format the login form in any way you like, you can even use forms authentication with browsers that do not use HTML, such as those on mobile devices. To do this, you need to detect the browser being used and provide a form in the correct format for the device (such as WML for mobile phones). Note that forms authentication uses standard HTML forms for collecting and submitting the user's credentials. Therefore, you must use SSL to encrypt and transmit the user's credentials securely. If you don't use SSL, the information is transmitted as clear text in the postback data in the request to the server.
Store User Information
Forms authentication stores users in the web.config file by default, but you can store the information anywhere you like. You just need to create the code that accesses the data store and retrieves the required information. A common approach is to store the user information in a custom database. ASP.NET's flexible storage options for user information enable you to control how you create and administer user accounts. It also lets you attach additional information to user accounts.
I've mentioned that forms authentication gives you substantial control over the interface that users use to log into your Web application. This approach has benefits, but also creates some extra work because you have to build the login page. Other forms of authentication supply some prebuilt portions. For example, if you're using Windows authentication, the browser provides a standard dialog box. In Passport authentication, the user interface of the Passport site is always used for logging in.
It's worth noting that forms authentication is merely a framework for building an authentication system, rather than an all-in-one system that's complete and ready to use. Fortunately, creating the login page for forms authentication doesn't require a lot of work.
The new Membership API, on the other hand, includes a prebuilt login control that you can use either on a separate login page or within any page of your application that provides a prebuilt login user interface. This user interface is customizable and communicates with the Membership API to log the user in automatically. The control does most of the work of creating custom login pages. In most cases, creating a custom login page requires nothing more than adding an ASPX page to your solution with a login control on it. You don't need to catch any events or write any code if you are fine with the default behavior of the control (which will usually be the case).
When you use forms authentication, you are responsible for maintaining the details of the users who access your system. The most important details are the credentials that the user needs in order to log into the system. Not only do you need to devise a way to store them, but you also need to ensure that they are stored securely. You also need to provide some sort of administration tools for managing the users stored in your custom store.
The Membership API framework ships with a prebuilt schema for storing credentials in a SQL Server database. So, you can save lots of time using this existing (and extensible) schema. Still, you are responsible for backing up the credentials store securely, so that you can restore it in case of a system failure.
Intercept Network Traffic
When a user enters credentials for forms authentication, the credentials are sent from the browser to the server in plain-text format. This means anyone intercepting them will be able to read them. This is obviously an insecure situation.
The usual solution to this problem is to use SSL. Now, a valid argument might be that you just need to use SSL for securing the login page, not the entire application. You can configure forms authentication to encrypt and sign the cookie, which makes it extremely difficult for an attacker to get any information from it. In addition, the cookie should not contain any sensitive information and therefore won't include the password that was used for authentication.
But what if the attacker intercepts the unencrypted traffic and picks only the (already encrypted) cookie and uses it for replay? The attacker doesn't need to decrypt it; she just needs to send the cookie with her own request across the wire. You can mitigate such a replay attack only if you run the entire website with SSL.
Other authentication mechanisms don't require this extra work. With Windows authentication, you can use a protocol that automatically enforces a secure login process (with the caveat that this is not supported by all browsers and all network environments). With Passport authentication, the login process is handled transparently by the Passport servers, which always use SSL.
Cookie authentication is a fairly straightforward system, on its surface. You might wonder why you shouldn't just implement it yourself using cookies or session variables.
The answer is, for the same reason developers don't implement features in ASP.NET ranging from session state to the Web control framework. Not only does ASP.NET save you the trouble, but it also provides an implementation that's secure, well tested, and extensible. Some of the advantages provided by ASP.NET's implementation of forms authentication include the fact that cookie authentication is secure and forms authentication integrates with the .NET security classes.
Cookie authentication seems simple, but you can be left with a highly insecure system if you don't implement this properly. On their own, cookies aren't a safe place to store sensitive information because a malicious user can view and edit cookie data easily. Attackers can compromise your system easily if your authentication is based on unprotected cookies.
By default, the forms authentication module encrypts its authentication information before placing it in a cookie. It also attaches a hash code and validates the cookies when the cookies return to the server to verify that no changes have been made. The combination of these two processes makes these cookies secure. It also saves you from having to write your own security code. Most examples of homemade cookie authentication are far less secure.
Forms authentication is an integral part of ASP.NET. As long as you keep up-to-date with patches, you should have a high level of protection. On the other hand, if you create your own cookie authentication system, you don't have the advantage of this widespread testing. The first time you'll notice a vulnerability will probably be when your system is compromised.
Use Forms Authentication Classes
All types of ASP.NET authentication use a consistent framework. Forms authentication is fully integrated with this security framework. For example, it populates the security context (IPrincipal) object and user identity (IIdentity) object, as it should. This makes it easy to customize the behavior of forms authentication.The most important part of the forms authentication framework is the FormsAuthenticationModule, which is an HttpModule class that detects existing forms authentication tickets in the request. If the ticket isn't available and the user requests a protected resource, it automatically redirects the request to the login page configured in your web.config file before this protected resource is even touched by the runtime.
If the ticket is present, the module automatically creates the security context by initializing the HttpContext.Current.User property with a default instance of GenericPrincipal, which contains a FormsIdentity instance with the name of the currently logged-in user. Basically, you don't work with the module directly. Your interface to the module consists of the classes that comprise the System.Web.Security namespace (see Table 1). For most cases, you use the FormsAuthentication class and the FormsIdentity class, which represents a successfully authenticated user in your application.
You now have the background you need to use forms authentication successfully. The next step is to implement forms authentication in an application. Forms authentication consists of three basic steps. First, configure forms authentication in the web.config file. Second, configure IIS to allow anonymous access to the virtual directory, and configure ASP.NET to restrict anonymous access to the Web application. Finally, create a custom login page that collects and validates a user name and password, and then interacts with the forms authentication infrastructure for creating the ticket. The remainder of the article walks you through how to implement these steps.
You have to configure forms authentication appropriately in your web.config file. Forms authentication works if you configure this section with the value Forms for the mode attribute:
<authentication mode="Forms">
<!-- Detailed configuration options -->
</authentication>
The <authentication /> configuration is limited to the top-level web.config file of your application. If the mode attribute is set to Forms, ASP.NET loads and activates the FormsAuthenticationModule, which does most of the work for you. The initial configuration basically uses default settings for forms authentication that are hard-coded into the ASP.NET runtime. You can override any default settings by adding settings to the <system.web> section of the machine.config file. You can override these default settings in your application by specifying additional settings in the <forms /> child tag of this section (see Table 2).
Credentials Store in web.config
You have your choice of where to store credentials for the users when using forms authentication. You can store them in a custom file or in a database; basically, you can store them anywhere you want if you provide the code for validating the user name and password entered by the user with the values stored in your credential store.
The easiest place to store credentials is directly in the web.config file through the <credentials /> subelement of the <forms /> configuration tag:
<authentication mode="Forms">
<!-- Detailed configuration options -->
<forms name="MyCookieName"
loginUrl="MyLogin.aspx"
timeout="20">
<credentials passwordFormat="Clear">
<user name=
"Admin" password="(Admin1)"/>
<user name="Mario"
password="Szpuszta"/>
<user name="Matthew"
password="MacDonald"/>
</credentials>
</forms>
</authentication>
Note that using web.config as a credential store is possible for simple solutions with only a few users. In larger scenarios, you should use the Membership API. Also, you can hash password values for credentials stored in the web.config file. Hashing is nothing more than applying one-way encryption to the password. This means you encrypt the password in a way that it can't be decrypted anymore.
You don't need to restrict access to pages in order to use authentication. It's possible to use authentication purely for personalization, so that anonymous users view the same pages as authenticated users (but see slightly different, personalized content). However, to demonstrate the redirection functionality of forms authentication, it's useful to create an example that denies access to anonymous users. This forces ASP.NET to redirect anonymous users to the login page.
To keep the example simple, use the technique of denying access to all unauthenticated users. Do this using the <authorization> element of the web.config file to add a new authorization rule:
<configuration>
<system.web>
<!-- Other settings omitted. -->
<authorization>
<deny users="?" />
</authorization>
</system.web>
</configuration>
The question mark is a wildcard character that matches all anonymous users. Including this rule in your web.config file specifies that anonymous users aren't allowed. Every user must be authenticated, and every user request will require the forms authentication ticket (which is a cookie). If you request a page in the application directory now, ASP.NET detects that the request isn't authenticated and attempts to redirect the request to the login page (which will probably cause an error, unless you've already created this page).
Note that the <authorization> element isn't limited to the web.config file in the root of the web application. Instead, you can use it in any subdirectory, thereby allowing you to set different authorization settings for different groups of pages.
The next step is to create a custom login page. This page collects a user name and password from the user and validates it against the credentials stored in the credential store. If you store the credentials in web.config, this is extremely easy (see Figure 1).
Note that this page also contains some validation controls (not visible). Such controls let the user enter only valid values for a user name and a password. This code lets you see all the controls contained on the login page (see Listing 1).
Supply Validation Controls
The validation controls serve two purposes. First, the RequiredFieldValidator controls ensure that both a user name and password are entered in a valid format containing only the characters allowed for user names and passwords. Second, the RegularExpressionValdiator controls ensure that only valid values are entered in the User Name text field and in the Password text field. For example, the user name may contain letters, digits, and spaces only:
ValidationExpression="[\w| ]*"
The \w character class is equivalent to [a-zA-Z_0-9], and the space afterward allows spaces in the user name. The password, for example, may also contain special characters:
ValidationExpression='[\w| !"ยง$%&/()=\-?\*]*'
You use the single quote for enclosing the attribute value because this uses the double quote, as the allowed special character. Furthermore, the attribute is contained in the tag code (and therefore the HTML entity), so & indicates that the ampersand (&) character is allowed in the password. The validation controls let you stop users from entering values for the user name or the password that would lead to a SQL injection attack. In addition to using parameterized SQL queries, you should always use validation controls to mitigate this type of attack in your applications.
The last step for creating the login page is to write the code for validating the credentials against the values entered by the user. You have to add the necessary code to the Click event of the login button:
protected void LoginAction_Click(object sender, EventArgs e)
{
Page.Validate();
if (!Page.IsValid) return;
if (FormsAuthentication.Authenticate(
UsernameText.Text, PasswordText.Text))
{
// Create the ticket, add the cookie to the
//response, and redirect to the originally
// requested page
FormsAuthentication.RedirectFromLoginPage(
UsernameText.Text, false);
}
else
{
// User name and password are not correct
LegendStatus.Text =
"Invalid username or password!";
}
}
Forms authentication uses standard HTML forms for entering credentials, so you send the user name and password over the network as plain text. This is an obvious security risk—anyone who intercepts the network traffic can read the user names and passwords that are entered into the login form. For this reason, it's strongly recommended that you encrypt the traffic between the browser and the server using SSL (as described in Chapter 19), at least while the user is accessing the login page.
It's also important to include the Page.IsValid condition at the beginning of this procedure. By default, validation controls use JavaScript for client-side validation. When calling Page.Validate(), the validation takes place on the server. This is important for browsers that either have JavaScript turned off or don't support it. If you don't include this part, validation doesn't occur if the browser doesn't support JavaScript or doesn't have JavaScript enabled. This means you should always include server-side validation in your code.
The FormsAuthentication class provides two methods that you use in this example. The Authenticate() method checks the specified user name and password against those stored in the web.config file and returns a Boolean value indicating whether a match was found. Remember that the methods of FormsAuthentication are static, so you don't need to create an instance of FormsAuthentication to use them—you simply access them through the name of the class:
if (FormsAuthentication.Authenticate(UsernameText.Text,
PasswordText.Text))
If you find a match for the supplied credentials, you can use the RedirectFromLoginPage() method:
FormsAuthentication.RedirectFromLoginPage(
UsernameText.Text, false);
This method performs several tasks at once. It creates an authentication ticket for the user; it encrypts the information from the authentication ticket; it creates a cookie to persist the encrypted ticket information; it adds the cookie to the HTTP response, sending it to the client; and it redirects the user to the originally requested page (which is contained in the query string parameter of the login page request's URL).
Implement a Persistent Cookie?
The second parameter of RedirectFromLoginPage() indicates whether a persistent cookie should be created. You store persistent cookies on the user's hard drive, enabling you to reuse them during later visits. Finally, if Authenticate() returns false, an error message is displayed on the page.
Logging a user out of forms authentication is as simple as calling the FormsAuthentication.SignOut() method:
protected void SignOutAction_Click(object sender, EventArgs e)
{
FormsAuthentication.SignOut();
FormsAuthentication.RedirectToLoginPage();
}
You remove the authentication cookie when you call the SignOut() method. Depending on the application, you might want to redirect the user to another page when the user logs out. If the user requests another restricted page, the request will be redirected to the login page. You can also redirect to the login page immediately after calling the sign-out method. Or, you can use the Response.Redirect method.
Forms authentication includes the possibility of storing the password in different formats. In the <credentials /> configuration section, you specify the format of the password with the passwordFormat attribute, which has three valid values: Clear, where you store passwords as clear text in the <user /> elements of the <credentials /> section; MD5, where you store the hashed version of the password in the <user /> elements, as well as the when the algorithm used to hash the password is the MD5 algorithm; and SHA1, where the <user /> elements in the <credentials /> section of the web.config file contain the hashed password, as well as when the algorithm used to hash the password is the SHA1 algorithm.
When using the hashed version of the passwords, you must write a tool or some code that hashes the passwords for you and stores the passwords in the web.config file. For storing the password, you should then use the FormsAuthentication.HashForStoringInConfigFile method instead of passing in the clear-text password:
string hashedPwd =
FormsAuthentication.HashForStoringInConfigFile(
clearTextPassword, "SHA1");
The first parameter specifies the clear-text password, and the second one specifies the hash algorithm you should use. The result of the method call is the hashed version of the password.
If you want to modify users stored in web.config, you must use the configuration API of the .NET Framework. You cannot edit this section with the Web-based configuration tool. This code snippet modifies the section through the configuration API:
'Configuration MyConfig =
WebConfigurationManager.OpenWebConfiguration(
"~/");
ConfigurationSectionGroup SystemWeb =
MyConfig.SectionGroups["system.web"];
AuthenticationSection AuthSec =
(AuthenticationSection)SystemWeb.Sections[
"authentication"];
AuthSec.Forms.Credentials.Users.Add(
new FormsAuthenticationUser(UserText.Text, PasswordText.Text));
MyConfig.Save();
Of course, you should allow only privileged users such as Web site administrators to execute the previous code, and the process executing the code must have write access to your web.config file. Also, you shouldn't include this sort of code in the Web application, but in an administration application only.
This should give you a good start for working with forms authentication. You've learned how to implement authentication systems that simplify coding and provide a great deal of flexibility. The online version of this article also delves into taking advantage of cookieless forms authentication, which ASP.NET 2.0 supports out of the box.