Working with Forms Authentication


If you want to set up a custom user registration system for your Web site, you can use Forms authentication. The advantage of this type of authentication is that it enables you to store usernames and passwords in whatever storage mechanism you desire . For example, you can store usernames and passwords in the Web.Config file, an XML file, or a database table.

Forms authentication relies on browser cookies to determine the identity of a user. After you enable Forms authentication for a directory, pages in the directory cannot be accessed unless the user has the proper Authentication Ticket stored in a cookie.

If a user requests a page without the proper Authentication Ticket, he or she can be automatically redirected to a login page. If the user enters a valid username and password combination, you can automatically redirect him or her back to the original page.

When using Forms authentication, you can easily set up an automatic user registration system. For example, you can create a database table that contains usernames and passwords. In that case, adding a new registered user is as simple as adding a new username and password to the database table.

The .NET classes for Forms authentication are located in the System.Web.Security namespace. The following list contains the most important of these classes:

  • FormsAuthentication This class contains several shared methods for working with Forms authentication.

  • FormsAuthenticationTicket This class represents the Authentication Ticket used in the cookie for Forms authentication.

  • FormsIdentity This class represents the identity of the user authenticated with Forms authentication.

  • FormsAuthenticationModule This class is the actual module used for forms authentication.

You learn how to take advantage of these classes in the following sections.

Enabling Forms Authentication

To enable basic Forms authentication for an application, you must complete the following three steps:

  • Set the authentication mode for the application by modifying the authentication section in the application root Web.Config file.

  • Deny access to anonymous users in one or more directories in the application by modifying the authorization section in the Web.Config files in the appropriate directories.

  • Create a login page containing a form that enables users to enter their usernames and passwords.

The first step is to enable Forms authentication for an application. To do so, you must modify an application's root directory Web.Config file. If the Web.Config file doesn't already exist, you can create it.

CAUTION

You must enable Forms authentication for an ASP.NET application as a whole. You can set the authentication mode only in the Web.Config file located in an application's root directory.

However, enabling Forms authentication does not force you to password-protect every page. You can enable anonymous access for particular directories within an application even when Forms authentication is enabled for the application.


NOTE

All the files discussed in this section are located in the FormSimple directory on the CD that accompanies this book. To use these files, copy the FormSimple directory and all its subdirectories to your Web server. Next , create a new virtual directory that points to the FormSimple directory.


The Web.Config file in Listing 19.1 contains the minimal amount of information necessary to enable Forms authentication for an application.

Listing 19.1 FormsSimple/Web.Co nfig
 <configuration>   <system.web>     <authentication mode="Forms" />   </system.web> </configuration> 

The C# version of this code can be found on the CD-ROM.

In Listing 19.1, the authentication mode is set to Forms . Creating this file enables Forms authentication for the entire application.

CAUTION

The names of the elements and attributes in the Web.Config file are case sensitive.


The next step is to password-protect individual directories. You can require users to log in to access any ASP.NET page at your Web site by modifying the root directory Web.Config file. Alternatively, you can add Web.Config files to particular directories to password-protect only certain pages.

To password-protect a particular directory and its subdirectories, add the Web.Config file in Listing 19.2 to the directory. (This file can be found in the FormsSimple\Secret subdirectory.)

Listing 19.2 FormsSimple\Secret\Web.Config
 <configuration>   <system.web>     <authorization>       <deny users="?" />     </authorization>   </system.web> </configuration> 

The C# version of this code can be found on the CD-ROM.

The Web.Config file in Listing 19.2 denies access to the ASP.NET pages contained in the directory to anonymous users. The ? symbol represents all anonymous users.

CAUTION

By default, Forms authentication applies only to ASP.NET pages. You cannot use it to password-protect other types of files, such as image files, Microsoft Word documents, text files, or Classic ASP files.

If you want to use Forms authentication with other types of files, then you must explicitly map the file type to the aspnet_isapi.dll extension in the Internet Services Manager.


After you add the Web.Config file in Listing 19.2 to a directory, anonymous users are denied access to ASP.NET pages in that directory and all subdirectories. If you want to enable users to access files in a particular subdirectory, you can add the Web.Config file contained in Listing 19.3. (This file can be found in the FormsSimple\Anon subdirectory.)

Listing 19.3 FormsSimple\Anon\Web.Config
 <configuration>   <system.web>     <authorization>       <allow users="?" />     </authorization>   </system.web> </configuration> 

The C# version of this code can be found on the CD-ROM.

The Web.Config file in Listing 19.3 allows all anonymous users to access any page contained in a directory and all the subdirectories of that directory.

The final step required for enabling Forms authentication is to create the Login.aspx page. If you attempt to access an ASP.NET page in a password-protected directory and you don't have the proper Authentication Ticket cookie, you are automatically redirected to this page.

The simple Login.aspx page contained in Listing 19.4 displays a form with a field for a username and password (see Figure 19.1). The page requires you to enter the username Sam and password Secret . (The form is case-sensitive.)

Listing 19.4 FormsSimple\Login.aspx
 <Script Runat="Server"> Sub Button_Click( s As Object, e As EventArgs )   If IsValid Then     If txtUsername.Text = "Sam" And txtPassword.Text = "Secret" Then       FormsAuthentication.RedirectFromLoginPage( txtUsername.Text, chkRemember.Checked )     Else       lblMessage.Text = "Bad username/password!"     End If   End If End Sub </Script> <html> <head><title>Login.aspx</title></head> <body> <form Runat="Server"> <h2>Please Login:</h2> <asp:Label   ID="lblMessage"   ForeColor="Red"   Font-Bold="True"   Runat="Server" /> <p> <b>Username:</b> <br> <asp:TextBox   ID="txtUsername"   Runat="Server" /> <asp:RequiredFieldValidator   ControlToValidate="txtUsername"   Text="Required!"   Runat="Server" /> <p> <b>Password:</b> <br> <asp:TextBox   ID="txtPassword"   Runat="Server" /> <asp:RequiredFieldValidator   ControlToValidate="txtPassword"   Text="Required!"   Runat="Server" /> <p> <asp:CheckBox   ID="chkRemember"   Runat="Server"/> Remember me with a cookie? <p> <asp:Button   Text="Login!"   OnClick="Button_Click"   Runat="Server" /> </form> </body> </html> 

The C# version of this code can be found on the CD-ROM.

Figure 19.1. A simple login page.

graphics/19fig01.jpg

In Listing 19.4, the important work happens in the Button_Click subroutine, which first checks the IsValid property to test whether both a username and password were entered into the form. If the page is valid, the values of the username and password form fields are matched against the values Sam and Secret .

If you enter the correct username and password, the RedirectFromLoginPage method is called. Two parameters are passed to this method: the username and a Boolean value indicating whether a persistent cookie should be created.

Forms authentication supports both session and persistent cookies. When you call the RedirectFromLoginPage , you can indicate whether a persistent cookie should be created. If the RedirectFromLoginPage creates a persistent cookie, the cookie continues to exist even if the user shuts down his or her computer and returns to your Web site many days in the future.

CAUTION

Normally, you should allow users to decide whether to create a persistent cookie. For example, if you were temporarily using a computer at the library or a friend's house, you would not want to create a persistent cookie on that computer.


Calling the RedirectFromLoginPage method performs two actions. First, it creates a cookie on the user's browser that contains an Authentication Ticket. After this cookie is set, the user can access pages in directories that require Forms authentication.

The RedirectFromLoginPage method also automatically redirects the user back to the page that sent him or her to the Login.aspx page in the first place by using a browser redirect.

NOTE

The RedirectFromLoginPage method redirects the user back to the page indicated by the ReturnUrl query string variable. If the user links directly to the Login.aspx page, the ReturnUrl query string variable doesn't have a value. In that case, the RedirectFromLoginPage redirects the user to the Default.aspx page.


Configuring Forms Authentication

In the preceding section, you learned how to modify the Web.Config file to enable Forms authentication for an application. In this section, you examine the options for configuring Forms authentication in more detail.

The authentication section in the Web.Config file can contain an optional forms element, which supports the following attributes:

  • loginUrl The page where the user is automatically redirected when authentication is required. By default, users are redirected to the Login.aspx page in the application root directory. However, you can change this attribute to point to any page that you please.

  • name The name of the browser cookie that contains the Authentication Ticket. By default, the cookie is named .ASPXAUTH . However, if you are configuring multiple applications on the same server, you should provide a unique cookie name for each application.

  • timeout The amount of time in minutes before a cookie expires . By default, this attribute has the value 30 minutes. This attribute does not apply to persistent cookies.

  • path The path used for the cookie. By default, this attribute has the value / .

  • protection The way the cookie data is protected. Possible values are All , None , Encryption , and Validation ; the default value is All .

The protection attribute requires some explanation. By default, cookies are encrypted by using either DES or TripleDES encryption (depending on the capabilities of the server). Furthermore, the contents of the cookie are validated with a Message Authentication Code to protect against tampering.

You can disable encryption or validation or both features by changing the value of the protection attribute. For example, setting protection to Encryption causes the cookie to be encrypted but not validated. You can get better performance from your application by disabling encryption and validation. However, disabling these features also results in a less secure site.

The Web.Config file in Listing 19.5 illustrates how you can set the forms attributes.

Listing 19.5 FormsAttributes\Web.Config
 <configuration>   <system.web>     <authentication mode="Forms">       <forms         name=".MyCookie"         loginUrl="/login/mylogin.aspx"         protection="All"         timeout="80"         path="/"/>     </authentication>   </system.web> </configuration> 

The C# version of this code can be found on the CD-ROM.

Configuring Forms Authorization

The authorization section of the Web.Config file determines which users can access ASP.NET pages within a directory. In the simplest case, you want to use the authorization section to deny all anonymous users access to the pages in a directory by using a Web.Config file like the one in Listing 19.6.

Listing 19.6 Web.Config
 <configuration>   <system.web>     <authorization>       <deny users="?" />     </authorization>   </system.web> </configuration> 

The C# version of this code can be found on the CD-ROM.

The authorization section can contain either <deny> elements, which deny access for particular users, or <allow> elements, which enable access for particular users. You can also use the special symbol ? , which stands for all anonymous users, or the symbol * , which stands for all users (both anonymous and authenticated).

You can use the authorization section to make fine-grained distinctions among users and among different actions the users might perform. For example, suppose that you want to deny access to all anonymous users. Furthermore, you want to deny access to James and Mark even after they log in. You can do so by using the Web.Config file in Listing 19.7.

Listing 19.7 Web.Config
 <configuration>   <system.web>     <authorization>       <deny users="?" />       <deny users="James,Mark" />     </authorization>   </system.web> </configuration> 

The C# version of this code can be found on the CD-ROM.

In Listing 19.7, the <deny> element> denies access to James and Mark. So, even if these users log in through the Login.aspx page, they cannot access ASP.NET pages located in the current directory.

Finally, you can use the verbs attribute to control whether users can use HTTP Post or HTTP Get . For example, the Web.Config file in Listing 19.8 enables anyone to get an ASP.NET page in the directory but prevents everyone except for James or Mark from posting an HTML form.

Listing 19.8 Web.Config
 <configuration>   <system.web>     <authorization>       <allow verbs="POST" users="James,Mark" />       <deny verbs="POST" users="*" />       <allow verbs="GET" users="*" />     </authorization>   </system.web> </configuration> 

The C# version of this code can be found on the CD-ROM.

Retrieving User Information

The identity of a user authenticated with Forms authentication is represented by the FormsIdentity class. You can use the following properties of this class to retrieve information about an authenticated user:

  • AuthenticationType Always returns the value Forms

  • IsAuthenticated Indicates whether the user was authenticated

  • Name Indicates the name of an authenticated user

  • Ticket Specifies the cookie Authentication Ticket associated with the current user

NOTE

You cannot retrieve one important bit of informationthe password associated with the user. There's a good reason for this limitation. The password is not actually stored as part of the cookie. If you want the password, you must look it up yourself.


You can use the IsAuthenticated property to test whether this user has already been authenticated. If a user requests a page from a directory that requires authentication and then requests a page from a directory that does not require authentication, the IsAuthenticated property continues to return the value True .

The Name property returns the name associated with the current user. Again, after a user is authenticated once, the Name property continues to hold the username.

Finally, the Ticket property represents the Authentication Ticket. The FormsAuthenticationTicket class has the following properties:

  • CookiePath The path of the Authentication Ticket cookie.

  • Expiration The date the Authentication Ticket cookie expires.

  • Expired A Boolean value indicating whether the current Authentication Ticket has expired.

  • IsPersistent A value that indicates whether the Authentication Ticket is contained in a persistent cookie.

  • IssueDate The date and time the cookie containing the Authentication Ticket was created.

  • Name The username associated with the Authentication Ticket.

  • UserData Custom data that you can include in the Authentication Ticket.

  • Version An integer representing the version number of the Authentication Ticket. Currently, by default, this property always returns the value 1 .

The ASP.NET page in Listing 19.9 demonstrates how you can retrieve and display all these properties within an ASP.NET page. (This page shows something interesting only if the user has already been authenticated before requesting the page, see Figure 19.2.)

Listing 19.9 FormsIdentity\FormsIdentity.aspx
 <%@ debug="True" %> <Script Runat="Server"> Sub Page_Load   Dim objUserIdentity As FormsIdentity   Dim objTicket As FormsAuthenticationTicket   If User.Identity.IsAuthenticated Then     objUserIdentity = User.Identity     objTicket = objUserIdentity.Ticket     lblName.Text = objUserIdentity.Name     lblExpiration.Text = objTicket.Expiration     lblExpired.Text = objTicket.Expired     lblIsPersistent.Text = objTicket.IsPersistent     lblIssueDate.Text = objTicket.IssueDate     lblUserData.Text = objTicket.UserData     lblVersion.Text = objTicket.Version   Else     lblName.Text = "Who Are You?"   End If End Sub </Script> <html> <head><title>FormsIdentity.aspx</title> <body> <asp:Label   ID="lblName"   Font-Size="18pt"   Font-Bold="True"   Runat="Server" /> <p> Expiration: <asp:Label   ID="lblExpiration"   Runat="Server" /> <p> Expired: <asp:Label   ID="lblExpired"   Runat="Server" /> <p> IsPersistent: <asp:Label   ID="lblIsPersistent"   Runat="Server" /> <p> IssueDate: <asp:Label   ID="lblIssueDate"   Runat="Server" /> <p> UserData: <asp:Label   ID="lblUserData"   Runat="Server" /> <p> Version: <asp:Label   ID="lblVersion"   Runat="Server" /> </body> </html> 

The C# version of this code can be found on the CD-ROM.

Figure 19.2. Displaying user information.

graphics/19fig02.jpg

Creating a Sign-Out Page

If you want to create a page that enables a user to sign out and return to anonymity, you can use the SignOut method of the FormsAuthentication class. Calling the SignOut method removes either a session or persistent cookie. A simple Sign-Out page is contained in Listing 19.10.

Listing 19.10 SignOut.aspx
 <Script Runat="Server"> Sub Page_Load   FormsAuthentication.SignOut() End Sub </Script> <html> <head><title>SignOut.aspx</title></head> <body> <h2>Goodbye!</h2> </body> </html> 

The C# version of this code can be found on the CD-ROM.

Authenticating Users with the Web.Config File

The Forms authentication classes are flexible enough to enable you to store usernames and passwords in many different storage mechanisms. If you need to store a small number of usernames and passwords, you can store them directly in the Web.Config file.

NOTE

All the files discussed in this section are located in the FormConfig directory on the CD that accompanies this book. To use these files, copy the FormConfig directory and all its subdirectories to your server. Next, you need to create a new virtual directory that has the FormConfig directory as its root directory.


To store the usernames and passwords, you need to add a credentials element to the authentication section in the Web.Config file. The file in Listing 19.11 illustrates how to add two username and password entries. (This page is included under the FormConfig directory on the CD-ROM.)

Listing 19.11 FormConfig\Web.Config
 <configuration>   <system.web>     <authentication mode="Forms">     <forms>       <credentials passwordFormat="Clear" >         <user name="Sam" password="Secret"/>         <user name="Fred" password="Secret"/>       </credentials>     </forms>     </authentication>     <authorization>       <deny users="?" />     </authorization>   </system.web> </configuration> 

The C# version of this code can be found on the CD-ROM.

The Web.Config file in Listing 19.11 contains both authentication and authorization sections. The authentication section contains a credentials section that contains two usernames and passwords. The authorization section prevents unauthenticated users from accessing any of the ASP.NET pages in the current directory.

NOTE

You must place the Web.Config file contained in Listing 19.11 in the root directory of your application.


To check whether a username and password combination exists in the Web.Config file, you can use the Authenticate method of the FormsAuthentication class. This method returns the value True if the combination exists, and False otherwise .

The Login.aspx page contained in Listing 19.12, for example, uses the Authenticate method to validate the username and password entered into a login form.

Listing 19.12 FormConfig\Login.aspx
 <Script Runat="Server"> Sub Button_Click( s As Object, e As EventArgs )   If IsValid Then     If FormsAuthentication.Authenticate( txtUsername.Text, txtPassword.Text ) Then       FormsAuthentication.RedirectFromLoginPage( txtUsername.Text, False )     Else       lblMessage.Text = "Bad username/password!"     End If   End If End Sub </Script> <html> <head><title>Login.aspx</title></head> <body> <form Runat="Server"> <h2>Please Login:</h2> <asp:Label   ID="lblMessage"   ForeColor="Red"   Font-Bold="True"   Runat="Server" /> <p> <b>Username:</b> <br> <asp:TextBox   ID="txtUsername"   Runat="Server" /> <asp:RequiredFieldValidator   ControlToValidate="txtUsername"   Text="Required!"   Runat="Server" /> <p> <b>Password:</b> <br> <asp:TextBox   ID="txtPassword"   Runat="Server" /> <asp:RequiredFieldValidator   ControlToValidate="txtPassword"   Text="Required!"   Runat="Server" /> <p> <asp:Button   Text="Login!"   OnClick="Button_Click"   Runat="Server" /> </form> </body> </html> 

The C# version of this code can be found on the CD-ROM.

In Listing 19.12, the Authenticate method is used in the Button_Click subroutine. If you neglect to check the Authenticate method before calling the RedirectFromLoginPage method, every user is authenticated regardless of the credential information in the Web.Config file.

Encrypting Passwords in the Web.Config File

In general, storing user passwords in clear text in the Web.Config file is not a wise idea. Any person who happens to be working on the Web server can open the Web.Config file and read everyone's secret passwords.

Instead of storing the passwords in clear text, you can encrypt the passwords by using either the SHA1 or MD5 hash algorithms. For example, storing the user credentials like this can be problematic :

 
 <credentials passwordFormat="Clear" >   <user name="Sam" password="Secret"/>   <user name="Fred" password="Secret"/> </credentials> 

Instead, you can store the credentials like this:

 
 <credentials passwordFormat="SHA1" >   <user name="Sam"     password="9AA4F59F833D419EF8FF2B44567645A282BCD6F0"/>   <user name="Fred"     password="9AA4F59F833D419EF8FF2B44567645A282BCD6F0"/> </credentials> 

In this example, you set the value of passwordFormat to SHA1 and enter the SHA1 hash values of the passwords.

Don't worry, you don't need to read a book on cryptography to compute the proper hash value for a password. The FormsAuthentication class includes the method (with the very long name) HashPasswordForStoringInConfigFile , which accepts two parameters: a plain text password to encode and the name of a hash algorithm ( SHA1 or MD5 ). The method returns the hashed password.

The page in Listing 19.13 uses the HashPasswordForStoringInConfigFile method to compute the hash value for any clear text password.

Listing 19.13 ComputeHash.aspx
[View full width]
 <Script Runat="Server"> Sub Button_Click( s As Object, e As EventArgs )   Dim strHashValue As String   strHashValue = FormsAuthentication.HashPasswordForStoringInConfigFile ( txtPassword.Text graphics/ccc.gif , lstAlgorithm.SelectedItem.Text )   lblHash.Text =  strHashValue End Sub </Script> <html> <head><title>ComputeHash.Aspx</title></head> <body> <form Runat="Server"> <h2>Compute Hash Value:</h2> <b>Password:</b> <br> <asp:TextBox   ID="txtPassword"   Runat="Server" /> <p> <b>Algorithm:</b> <br> <asp:ListBox   ID="lstAlgorithm"   Runat="Server">   <asp:ListItem Text="SHA1" />   <asp:ListItem Text="MD5" /> </asp:ListBox> <p> <asp:Button   Text="Compute!"   OnClick="Button_Click"   Runat="Server" /> <hr> Hash Value: <br> <asp:Label   ID="lblHash"   Runat="Server" /> </form> </body> </html> 

The C# version of this code can be found on the CD-ROM.

Authenticating Users with an XML File

Instead of using the Web.Config file to store usernames and passwords, you can store the usernames and passwords in an XML file. In this section, you learn how to store credentials in an XML file named Passwords.xml .

NOTE

All the files discussed in this section are located in the FormsXml directory on the CD that accompanies this book. To use these files, copy the FormsXml directory and all its subdirectories to your server. Next, create a new virtual directory that has the FormsXml directory as its root directory.


For this example, you need to create four files:

  • Web.Config This file contains configuration information for authentication and authorization.

  • Passwords.xml This file stores usernames and passwords.

  • Login.aspx This page validates a username and password combination against the Password.xml file.

  • Register.aspx This page enables a user to register at the Web site and add a new username and password to the Passwords.xml file.

The first file should be familiar; it's the standard Web.Config file for Forms authentication. This file is included in Listing 19.14.

Listing 19.14 FormsXml\Web.Config
 <configuration>   <system.web>     <authentication mode="Forms" />     <authorization>       <deny users="?" />     </authorization>   </system.web> </configuration> 

The C# version of this code can be found on the CD-ROM.

Next, you need to create the Passwords.xml file in which you can store all the usernames and passwords. The Passwords.xml file is contained in Listing 19.15.

Listing 19.15 FormsXml\Passwords.xml
 <Passwords>   <user>     <name>Sam</name>     <password>Secret</password>   </user>   <user>     <name>Fred</name>     <password>Secret</password>   </user> </Passwords> 

The C# version of this code can be found on the CD-ROM.

When you validate a username and password, the username and password combination is checked against the Passwords.xml file. When a new user registers, the new user's username and password are added to this file.

The Login.aspx page is contained in Listing 19.16.

Listing 19.16 FormsXml\Login.aspx
 <%@ Import Namespace="System.Data" %> <Script Runat="Server"> Sub Page_Load   Dim strLinkPath As String   If Not IsPostBack Then     strLinkPath = String.Format( "Register/Register.aspx?ReturnUrl={0}", _       Request.Params( "ReturnUrl" ) )     lnkRegister.NavigateUrl = String.Format( strLinkPath )   End If End Sub Sub Button_Click( s As Object, e As EventArgs )   If IsValid Then     If XmlAuthenticate( txtUsername.Text, txtPassword.Text ) Then       FormsAuthentication.RedirectFromLoginPage( txtUsername.Text, False )     End If   End If End Sub Function XmlAuthenticate( strUsername As String, strPassword As String ) As Boolean   Dim dstPasswords As DataSet   Dim dtblPasswords As DataTable   Dim arrUsers() As DataRow   dstPasswords = New DataSet()   dstPasswords.ReadXml( MapPath( "Passwords.xml" ) )   dtblPasswords = dstPasswords.Tables( 0 )   arrUsers = dtblPasswords.Select( "name='" & strUsername & "'" )   If arrUsers.Length > 0 Then     If arrUsers( 0 )( "password" ) = strPassword Then       Return True     Else       lblMessage.Text = "Incorrect Password!"     End If   Else     lblMessage.Text = "Username Not Found!"   End If   Return False End Function </Script> <html> <head><title>Login.aspx</title></head> <body> <form Runat="Server"> <h2>Please Login:</h2> <asp:Label   ID="lblMessage"   ForeColor="Red"   Font-Bold="True"   Runat="Server" /> <p> <b>Username:</b> <br> <asp:TextBox   ID="txtUsername"   Runat="Server" /> <asp:RequiredFieldValidator   ControlToValidate="txtUsername"   Text="Required!"   Runat="Server" /> <p> <b>Password:</b> <br> <asp:TextBox   ID="txtPassword"   Runat="Server" /> <asp:RequiredFieldValidator   ControlToValidate="txtPassword"   Text="Required!"   Runat="Server" /> <p> <asp:Button   Text="Login!"   OnClick="Button_Click"   Runat="Server" /> <hr> <asp:HyperLink   ID="lnkRegister"   Text="Click Here To Register!"   Runat="Server" /> </form> </body> </html> 

The C# version of this code can be found on the CD-ROM.

The page in Listing 19.16 contains a function named XmlAuthenticate . This function returns the value True if the username and password entered into the HTML form exist in the Passwords.xml file; otherwise, the function returns the value False .

The function loads the Passwords.xml file into a DataSet with the ReadXml method. Next, it uses the DataSet 's Select method to retrieve any entries in the Passwords.xml file in which the name element matches the name entered into the form. If the username and password exist, the function returns the value True .

If XmlAuthenticate returns the value True , the RedirectFromLoginPage method is called. This method issues a new cookie containing an Authentication Ticket and automatically redirects the user's browser back to the page that was originally requested .

Notice that the Login.aspx page includes a link to the Register.aspx page. The user can click this link to register a new username and password for the Web site.

A query string variable named ReturnUrl is added to the link to Register.aspx in the Page_Load subroutine. This query string variable contains the path to the original page that the user requested before being redirected to the Login.aspx page.

The Register.aspx page is contained in a separate directory from the Login.aspx page. Storing pages separately is necessary because all the pages, except the Login.aspx page, require authentication to access. The Register.aspx page, shown in Listing 19.17, is contained in a directory that includes a Web.Config file that enables access for all users.

Listing 19.17 FormsXml\Register\Register.aspx
 <%@ Import Namespace="System.Data" %> <Script Runat="Server"> Sub Button_Click( s As Object, e As EventArgs )   Dim dstPasswords As DataSet   Dim dtblPasswords As DataTable   Dim arrUsers() As DataRow   Dim drowNew As DataRow   If IsValid Then     dstPasswords = New DataSet()     dstPasswords.ReadXml( MapPath( "../Passwords.xml" ) )     dtblPasswords = dstPasswords.Tables( 0 )     arrUsers = dtblPasswords.Select( "Name='" & txtUsername.Text & "'" )     If arrUsers.Length > 0 Then       lblMessage.Text = "Username Already Registered!"     Else       drowNew = dtblPasswords.NewRow()       drowNew( "Name" ) = txtUsername.Text       drowNew( "Password" ) = txtPassword.Text       dtblPasswords.Rows.Add( drowNew )       dstPasswords.WriteXml( MapPath( "../Passwords.xml" ) )       FormsAuthentication.RedirectFromLoginPage( txtUsername.Text, False )     End If   End If End Sub </Script> <html> <head><title>Register.aspx</title></head> <body> <form Runat="Server"> <h2>Please Register</h2> <asp:Label   ID="lblMessage"   ForeColor="Red"   Font-Bold="True"   EnableViewState="False"   Runat="Server" /> <p> <b>Username:</b> <br> <asp:TextBox   ID="txtUsername"   Runat="Server" /> <asp:RequiredFieldValidator   ControlToValidate="txtUsername"   Text="Required!"   Runat="Server" /> <p> <b>Password:</b> <br> <asp:TextBox   ID="txtPassword"   Runat="Server" /> <asp:RequiredFieldValidator   ControlToValidate="txtPassword"   Text="Required!"   Runat="Server" /> <p> <asp:Button   Text="Register!"   OnClick="Button_Click"   Runat="Server" /> </form> </body> </html> 

The C# version of this code can be found on the CD-ROM.

The Register.aspx page, like the Login.aspx page, contains a form with fields for a username and password. When the form is submitted, the Button_Click subroutine is executed.

Before doing anything else, the Button_Click subroutine checks whether the username entered into the form already exists in the Passwords.xml file. You don't want the same username registered by more than one user. If the username has already been registered, an error is displayed.

If the username has not been registered, the new username and password are added to the Passwords.xml file. Next, the RedirectFromLoginPage method is called to send the user back to the original page he or she requested.

Authenticating Users with a Database Table

The most common method of creating a custom user registration system is to use a database table to hold usernames and passwords. For large Web sites, this solution is the best because it is the most scalable.

NOTE

All the files discussed in this section are located in the FormsDB directory on the CD that accompanies this book. To use these files, you need to copy the FormsDB directory and all its subdirectories to your server. Next, create each of the database objects. Finally, you need to create a new virtual directory that points to FormsDB .


In this section, you are going to create a user registration system with Forms authentication that works with Microsoft SQL Server. To create this system, you need to create the following objects:

  • Web.Config This configuration file configures authentication and authorization for Forms authentication.

  • UserList database table This database table contains the username, password, and unique ID value for each user.

  • Login.aspx This ASP.NET page validates usernames and passwords by checking them against the contents of the UserList database table.

  • DBAuthenticate stored procedure This SQL Server stored procedure is used by the Login.aspx page to check for a username and password in the UserList table.

  • Register.aspx This ASP.NET page enables new users to register usernames and passwords by adding them to the UserList table.

  • DBRegister stored procedure This SQL Server stored procedure is used by the Register.aspx page to add a new username and password to the UserList table.

You use SQL Server stored procedures to validate usernames and passwords and to register new users for performance reasons.

The first file that you need to create is Web.Config . This file, included in Listing 19.18, should be familiar to you because it contains the standard sections to enable Forms authentication.

Listing 19.18 FormsDB\Web.Config
 <configuration>   <system.web>     <authentication mode="Forms" />     <authorization>       <deny users="?" />     </authorization>   </system.web> </configuration> 

The C# version of this code can be found on the CD-ROM.

You store usernames and passwords in a SQL Server database table. You can create this table by using either the SQL Enterprise Manager or SQL Query Analyzer. The SQL command used to create the UserList table is contained in Listing 19.19.

Listing 19.19 FormsDB\UserList.sql
 CREATE TABLE UserList (   u_id INT NOT NULL IDENTITY,   u_username VARCHAR( 100 ),   u_password VARCHAR( 100 ) ) 

The C# version of this code can be found on the CD-ROM.

Next, you need to create the Login.aspx page. This page, contained in Listing 19.20, has a form that enables users to log in with their usernames and passwords.

Listing 19.20 FormsDB\Login.aspx
 <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.SqlClient" %> <Script Runat="Server"> Sub Page_Load   Dim strLinkPath As String   If Not IsPostBack Then     strLinkPath = String.Format( "Register/Register.aspx?ReturnUrl={0}", _       Request.Params( "ReturnUrl" ) )     lnkRegister.NavigateUrl = String.Format( strLinkPath )   End If End Sub Sub Button_Click( s As Object, e As EventArgs )   If IsValid Then     If DBAuthenticate( txtUsername.Text, txtPassword.Text ) > 0 Then       FormsAuthentication.RedirectFromLoginPage( txtUsername.Text, False )     End If   End If End Sub Function DBAuthenticate( strUsername As String, strPassword As String ) As Integer   Dim conMyData As SqlConnection   Dim cmdSelect As SqlCommand   Dim parmReturnValue As SqlParameter   Dim intResult As Integer   conMyData = New SqlConnection( "Server=localhost;UID=sa;PWD=secret; Database=myData" )   cmdSelect = New SqlCommand( "DBAuthenticate", conMyData )   cmdSelect.CommandType = CommandType.StoredProcedure   parmReturnValue = cmdSelect.Parameters.Add( "RETURN_VALUE", SqlDbType.Int )   parmReturnValue.Direction = ParameterDirection.ReturnValue   cmdSelect.Parameters.Add( "@username", strUsername )   cmdSelect.Parameters.Add( "@password", strPassword )   conMyData.Open()     cmdSelect.ExecuteNonQuery()     intResult = cmdSelect.Parameters( "RETURN_VALUE" ).Value   conMyData.Close()   If intResult < 0 Then     If intResult = -1 Then       lblMessage.Text = "Username Not Registered!"     Else       lblMessage.Text = "Invalid Password!"     End If   End If   Return intResult End Function </Script> <html> <head><title>Login.aspx</title></head> <body> <form Runat="Server"> <h2>Please Login:</h2> <asp:Label   ID="lblMessage"   ForeColor="Red"   Font-Bold="True"   Runat="Server" /> <p> <b>Username:</b> <br> <asp:TextBox   ID="txtUsername"   Runat="Server" /> <asp:RequiredFieldValidator   ControlToValidate="txtUsername"   Text="Required!"   Runat="Server" /> <p> <b>Password:</b> <br> <asp:TextBox   ID="txtPassword"   Runat="Server" /> <asp:RequiredFieldValidator   ControlToValidate="txtPassword"   Text="Required!"   Runat="Server" /> <p> <asp:Button   Text="Login!"   OnClick="Button_Click"   Runat="Server" /> <hr> <asp:HyperLink   ID="lnkRegister"   Text="Click Here To Register!"   Runat="Server" /> </form> </body> </html> 

The C# version of this code can be found on the CD-ROM.

The Login.aspx page contains a function named DBAuthenticate . This function matches the username and password entered into the login form against the UserList database table with the DBAuthenticate stored procedure. If the user can be authenticated, the RedirectFromLoginPage method is called. This method issues a new Authentication Ticket cookie and redirects the user back to the original page he or she requested. If the user cannot be found, an error message is displayed.

The Login.aspx page uses a stored procedure named DBAuthenticate . This stored procedure is contained in Listing 19.21.

Listing 19.21 FormsDB\DBAuthenticate.sql
 CREATE PROCEDURE DBAuthenticate (   @username Varchar( 100 ),   @password Varchar( 100 ) ) As DECLARE @ID INT DECLARE @actualPassword Varchar( 100 ) SELECT   @ID = IdentityCol,   @actualPassword = u_password   FROM UserList   WHERE u_username = @username IF @ID IS NOT NULL   IF @password = @actualPassword     RETURN @ID   ELSE     RETURN - 2 ELSE   RETURN - 1 

The C# version of this code can be found on the CD-ROM.

The DBAuthenticate stored procedure returns one of three values. If the username and password passed to the procedure exist in the UserList table, the stored procedure returns the ID of the user. If the username cannot be found, the procedure returns the value -1 . If the username is found but the password is invalid, the procedure returns -2 .

The Login.aspx page contains a link to the Register.aspx page, which enables new users to register at your Web site. The code for the Register.aspx page is contained in Listing 19.22.

NOTE

The Register.aspx page is contained in its own directory with its own Web.Config file. The Web.Config file enables anonymous access to files in the directory. Enabling access this way is necessary to enable unauthenticated users to register. (You could also enable anonymous access to the Register.aspx page with the location element in the root Web.Config file.)


Listing 19.22 FormsDB\Register\Register.aspx
 <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.SqlClient" %> <Script Runat="Server"> Sub Button_Click( s As Object, e As EventArgs )   Dim conMyData As SqlConnection   Dim cmdSelect As SqlCommand   Dim parmReturnValue As SqlParameter   Dim intResult As Integer   If IsValid Then     conMyData = New SqlConnection( "Server=localhost;UID=sa;PWD=secret; Database=myData" )     cmdSelect = New SqlCommand( "DBRegister", conMyData )     cmdSelect.CommandType = CommandType.StoredProcedure     parmReturnValue = cmdSelect.Parameters.Add( "RETURN_VALUE", SqlDbType.Int )     parmReturnValue.Direction = ParameterDirection.ReturnValue     cmdSelect.Parameters.Add( "@username", txtUsername.Text )     cmdSelect.Parameters.Add( "@password", txtPassword.Text )     conMyData.Open()       cmdSelect.ExecuteNonQuery()       intResult = cmdSelect.Parameters( "RETURN_VALUE" ).Value     conMyData.Close()     If intResult = - 1 Then       lblMessage.Text = "Username Already Registered!"     Else       FormsAuthentication.RedirectFromLoginPage( txtUsername.Text, False )     End If   End If End Sub </Script> <html> <head><title>Register.aspx</title></head> <body> <form Runat="Server"> <h2>Please Register</h2> <asp:Label   ID="lblMessage"   ForeColor="Red"   Font-Bold="True"   EnableViewState="False"   Runat="Server" /> <p> <b>Username:</b> <br> <asp:TextBox   ID="txtUsername"   Runat="Server" /> <asp:RequiredFieldValidator   ControlToValidate="txtUsername"   Text="Required!"   Runat="Server" /> <p> <b>Password:</b> <br> <asp:TextBox   ID="txtPassword"   Runat="Server" /> <asp:RequiredFieldValidator   ControlToValidate="txtPassword"   Text="Required!"   Runat="Server" /> <p> <asp:Button   Text="Register!"   OnClick="Button_Click"   Runat="Server" /> </form> </body> </html> 

The C# version of this code can be found on the CD-ROM.

The Register.aspx page contained in Listing 19.22 has one main subroutine named Button_Click . This subroutine adds a new username and password to the UserList table if the username does not already exist. The Button_Click subroutine uses a SQL stored procedure named DBRegister , contained in Listing 19.23, to add the new user information.

Listing 19.23 FormsDB\DBRegister.sql
 CREATE PROCEDURE DBRegister (   @username Varchar( 100 ),   @password Varchar( 100 ) ) AS IF EXISTS( SELECT u_id   FROM UserList   WHERE u_username=@username )   RETURN - 1 ELSE   INSERT UserList (     u_username,     u_password     ) VALUES (     @username,     @password     )   RETURN @@IDENTITY 

The C# version of this code can be found on the CD-ROM.

The DBRegister stored procedure contained in Listing 19.23 returns one of two values. If the username passed to the stored procedure already exists in the UserList table, the procedure returns the value -1 . Otherwise, the stored procedure adds the new username and password and returns the ID for the user.

Implementing Roles-based Authentication

In certain situations, it makes sense to divide the users of your Web site into different roles. Dividing users into roles is useful when you want to grant or deny different permissions to different groups of users. For example, you might want to permit users in the supervisors role to edit certain data that users in the sales role cannot.

NOTE

All the files discussed in this section are located in the FormsRoles directory on the CD that accompanies this book. To use these files, you need to copy the FormsRoles directory to your server. Next, create a new virtual directory that points to the FormsRoles directory.


You can assign authenticated users to different roles within the Application_AuthenticateRequest subroutine in the Global.asax file. To assign a set of roles to a user, you use statements like the following:

 
 Dim arrRoles As String() = { "Supervisors", "Engineers" } Context.User = New GenericPrincipal( Context.User.Identity, arrRoles ) 

The first statement creates a new string array of role names. The second statement associates the current authenticated user with the list of roles.

Imagine that you have created an XML file that contains a list of roles for certain users of your Web site. This XML file is contained in Listing 19.24.

Listing 19.24 FormsRoles\Roles.xml
 <roles>   <user     name="Bob"     roles="Sales" />   <user     name="Jane"     roles="Supervisor,Sales" /> </roles> 

The C# version of this code can be found on the CD-ROM.

The XML file in Listing 19.24 associates Bob with the Sales role and Jane with the Supervisor role and Sales roles.

After you create the Roles.xml file, you can use the file to assign users to different roles with the Global.asax file in Listing 19.25.

Listing 19.25 FormsRoles\Global.asax
 <%@ Import Namespace="System.Security.Principal" %> <%@ Import Namespace="System.Xml" %> <Script Runat="Server"> Sub Application_AuthenticateRequest( s As Object, e As EventArgs )   Dim strUserName As String   Dim objRoles As XmlDocument   Dim arrRoles As String()   Dim objNode As XmlNode   Dim strXPath As String   objRoles = GetRoles()   If Context.Request.IsAuthenticated Then     strUserName = Context.User.Identity.Name     strXPath = String.Format( "user[@name='{0}']", strUserName )     objNode = objRoles.DocumentElement.SelectSingleNode( strXPath )     If Not IsNothing( objNode ) Then       arrRoles = objNode.Attributes( "roles" ).Value.Split       Context.User = New GenericPrincipal( Context.User.Identity, arrRoles )     End If   End If End Sub Function GetRoles() As XmlDocument   Dim objRoles As XmlDocument   objRoles = Context.Cache( "Roles" )   If objRoles Is Nothing Then     objRoles = New XmlDocument     objRoles.Load( Server.MapPath( "Roles.xml" ) )     Context.Cache.Insert( _       "Roles", _       objRoles, _       New CacheDependency( Server.MapPath( "Roles.xml" ) ) )   End If   Return objRoles End Function </Script> 

The C# version of this code can be found on the CD-ROM.

The Global.asax file in Listing 19.25 contains one function and one subroutine. The function, named GetRoles() , retrieves an XmlDocument that represents the Roles.xml file. This file is either retrieved from the cache or from the file system.

The Application_AuthenticateRequest subroutine is fired whenever a user requests a page. This subroutine retrieves the list of roles associated with the current users from the list of roles returned by the GetRoles() function. Next, the subroutine assigns the list of roles to the current user.

You can test the files in Listings 19.24 and 19.25 with the page in Listing 19.26.

Listing 19.26 FormsRoles\Default.aspx
 <html> <head><title>Default.aspx</title></head> <body> <h1>Welcome <%=User.Identity.Name%></h1> <% If User.IsInRole( "Supervisor" ) Then   Response.Write( "You have supervisor permissions!" ) Else   Response.Write( "You don't have supervisor permissions!" ) End If %> </body> </html> 

The C# version of this code can be found on the CD-ROM.

The page in Listing 19.26 uses the IsInRole method to display different messages depending on whether the current user is a member of the Supervisor role or not.

Creating a Custom Authentication Ticket

Forms authentication uses an Authentication Ticket stored in a cookie to authenticate users each time they request a page. The Authentication Ticket is automatically generated and added to a user's browser by the RedirectFromLoginPage method.

In most cases, the Authentication Ticket automatically generated by the RedirectFromLoginPage method contains all the information you need. You can use this ticket to retrieve such information as the username and date the cookie was issued. However, you also have the option of creating the Authentication Ticket yourself.

Why would you want to create the Authentication Ticket manually? If you create it yourself, you can add additional information to it by using the UserData property. For example, you can use the UserData property to store a unique ID or the email address for each user. After the information is stored in the Authentication Ticket, you can retrieve this information from the ticket on each page the user requests.

CAUTION

An Authentication Ticket is stored in a browser cookie, and browser cookies have size limitations. For example, the original Netscape cookie specification places a 4KB limit on the size of any cookie (the size refers to the combined size of the cookie's name and value).


The Authentication Ticket is represented by the FormsAuthenticationTicket class. If you want to create an Authentication Ticket that contains custom data, you need to explicitly create an object of this class and add it to the browser's cookie collection.

You can explicitly create a new Authentication Ticket by passing the following parameters to the FormsAuthenticationTicket constructor:

  • version The version of the Authentication Ticket. Typically, you supply the value 1 .

  • name The username associated with the Authentication Ticket (a maximum of 32 bytes).

  • issueDate The original date the cookie was issued.

  • expiration The date the cookie expires.

  • isPersistent A Boolean value indicating whether the cookie is a session or persistent cookie.

  • userData A string that can contain any data you please.

To illustrate the process of creating a custom Authentication Ticket, you are going to create three files:

  • Web.Config This standard configuration file enables Forms authentication.

  • Login.aspx This page holds custom data added to an Authentication Ticket.

  • Default.aspx This page retrieves and displays the custom data from the Authentication Ticket.

NOTE

All the files discussed in this section are located in the AuthenticationTicket directory on the CD that accompanies this book. To use these files, copy the AuthenticationTicket directory to your server and create a new virtual directory that points to it.


The standard Web.Config file for this example is contained in Listing 19.27.

Listing 19.27 AuthenticationTicket\Web.Config
 <configuration>   <system.web>     <authentication mode="Forms" />     <authorization>       <deny users="?" />     </authorization>   </system.web> </configuration> 

The C# version of this code can be found on the CD-ROM.

If an unauthenticated user attempts to access a page in a directory that contains the Web.Config file in Listing 19.27, the user is automatically redirected to the Login.aspx page. This page is contained in Listing 19.28.

Listing 19.28 AuthenticationTicket\Login.aspx
 <Script Runat="Server"> Sub Button_Click( s As Object, e As EventArgs )   Dim objTicket As FormsAuthenticationTicket   Dim objCookie As HttpCookie   Dim strReturnURL As String   objTicket = New FormsAuthenticationTicket( _     1, _     txtUsername.Text, _     NOW(), _     #12/25/2002#, _     False, _     txtUserdata.Text )   objCookie = New HTTPCookie( ".ASPXAUTH" )   objCookie.Value = FormsAuthentication.Encrypt( objTicket )   Response.Cookies.Add( objCookie )   strReturnURL = Request.Params( "ReturnURL" )   If strReturnURL <> Nothing Then     Response.Redirect( strReturnURL )   Else     Response.Redirect( "Default.aspx" )   End If End Sub </Script> <html> <head><title>Login.aspx</title></head> <body> <form Runat="Server"> <b>Username:</b> <br> <asp:TextBox   ID="txtUsername"   Runat="Server" /> <p> <b>User Data:</b> <br> <asp:TextBox   ID="txtUserdata"   Runat="Server" /> <p> <asp:button   Text="Add Ticket!"   OnClick="Button_Click"   Runat="Server" /> </form> </body> </html> 

The C# version of this code can be found on the CD-ROM.

The Login.aspx page in Listing 19.28 contains a form for entering a username and custom user data. When the form is submitted, the Button_Click subroutine is executed.

The Button_Click subroutine creates a new Authentication Ticket named objTicket . The username and userdata form fields supply the values of the name and userData properties of the new ticket.

Next, the custom Authentication Ticket is added to the browser's cookie collection with the Add method of the HttpCookiesCollection object.

Finally, the user is redirected back to the page indicated by the ReturnURL query string variable. If this query string variable doesn't exist, the user is redirected to the Default.aspx page.

To test whether the custom user data was actually added to the Authentication Ticket, you can use the Default.aspx page contained in Listing 19.29.

Listing 19.29 AuthenticationTicket\Default.aspx
 <Script Runat="Server"> Sub Page_Load   Dim objFormsID As FormsIdentity   objFormsID = User.Identity   lblName.Text = objFormsID.Ticket.Name   lblUserData.Text = objFormsID.Ticket.UserData End Sub </Script> <html> <head><title>Default.aspx</title></head> <body> <b>Name:</b> <asp:Label   ID="lblName"   Runat="Server" /> <p> <b>User Data:</b> <asp:Label   ID="lblUserData"   Runat="Server" /> </body> </html> 

The C# version of this code can be found on the CD-ROM.

In the Page_Load subroutine in Listing 19.29, the identity of the current user is retrieved from the User property of the Page class. The identity of the current user is represented by an instance of the FormsIdentity class, which includes the Authentication Ticket.

Both the name and custom user data associated with the current user are retrieved from the instance of the FormsIdentity class. Both bits of information are displayed in Label controls.

Using Forms Authentication and Web Farms

By default, you cannot share the same Authentication Ticket cookie across multiple servers. This means that, without some extra work, you cannot use Forms Authentication with servers clustered into a Web farm.

If you want to use Forms Authentication across multiple servers, you need to configure all the servers to share the same validation and decryption keys. By default, each server automatically generates its own keys. To force multiple servers to use common keys, modify the following machineKey section in either an application root Web.Config file or the Machine.Config file:

 
 <machineKey   validationKey="AutoGenerate, IsolateApps"   decryptionKey="AutoGenerate, IsolateApps"   validation="SHA1"/> 

You need to replace autogenerate with a valid key. The key must be between 40 and 128 characters (the recommendation is 128 characters). You can pick any random string of characters for the validationKey and decryptionKey values.

NOTE

The IsolateApps modifier was introduced with version 1.1 of the ASP.NET Framework. It causes different keys to be generated for different applications running on the same server. If you need to share the same keys across multiple ASP.NET applications, then you need to remove the IsolateApps modifier.




ASP.NET Unleashed
ASP.NET 4 Unleashed
ISBN: 0672331128
EAN: 2147483647
Year: 2003
Pages: 263

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net