ASP.NET 2.0 Unleashed

ASP.NET Membership enables you to create new users, delete users, and edit user properties. It's the framework that is used behind the scenes by the Login controls.

ASP.NET Membership picks up where Forms authentication leaves off. Forms authentication provides you with a way of identifying users. ASP.NET Membership is responsible for representing the user information.

ASP.NET Membership uses the provider model. The ASP.NET Framework includes two Membership providers:

  • SqlMembershipProvider Stores user information in a Microsoft SQL Server database.

  • ActiveDirectoryMembershipProvider Stores user information in the Active Directory or an Active Directory Application Mode server.

In this section, you learn how to use the ASP.NET Membership application programming interface. You learn how to use the Membership class to modify membership information programmatically.

You also learn how to configure both the SqlMembershipProvider and the ActiveDirectoryMembershipProvider. For example, you learn how to modify the requirements for a valid membership password.

Finally, we build a custom Membership provider. It is an XmlMembershipProvider that stores membership information in an XML file.

Using the Membership Application Programming Interface

The main application programming interface for ASP.NET Membership is the Membership class. This class supports the following methods:

  • CreateUser Enables you to create a new user.

  • DeleteUser Enables you to delete an existing user.

  • FindUsersByEmail Enables you to retrieve all users who have a particular email address.

  • FindUsersByName Enables you to retrieve all users who have a particular username.

  • GeneratePassword Enables you to generate a random password.

  • GetAllUsers Enables you to retrieve all users.

  • GetNumberOfUsersOnline Enables you to retrieve a count of all users online.

  • GetUser Enables you to retrieve a user by username.

  • GetUserNameByEmail Enables you to retrieve the username for a user with a particular email address.

  • UpdateUser Enables you to update a user.

  • ValidateUser Enables you to validate a username and password.

This class also supports the following event:

  • ValidatingPassword Raised when a user password is validated. You can handle this event to implement a custom validation algorithm.

You can use the methods of the Membership class to administer the users of your website. For example, the page in Listing 21.15 displays a list of every registered user (see Figure 21.5).

Figure 21.5. Displaying registered users.

Listing 21.15. ListUsers.aspx

<%@ Page Language="VB" %> <!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>List Users</title> </head> <body> <form runat="server"> <div> <asp:GridView DataSource Runat="server" /> <asp:ObjectDataSource TypeName="System.Web.Security.Membership" SelectMethod="GetAllUsers" Runat="server" /> </div> </form> </body> </html>

In Listing 21.15, an ObjectDataSource control is used to represent the Membership class. The GetAllUsers() method is called to get the list of users.

You also can use the methods of the Membership class to create custom Login controls. For example, notice that you can retrieve the number of users currently online by calling the GetNumberOfUsersOnline() method. The custom control in Listing 21.16 displays the value returned by this method.

Note

Chapter 31, "Building Custom Controls," discusses custom control building.

Listing 21.16. UsersOnline.vb

Imports System.Web.Security Imports System.Web.UI Imports System.Web.UI.WebControls Namespace myControls ''' <summary> ''' Displays Number of Users Online ''' </summary> Public Class UsersOnline Inherits WebControl Protected Overrides Sub RenderContents(ByVal writer As HtmlTextWriter) writer.Write(Membership.GetNumberOfUsersOnline()) End Sub End Class End namespace

The page in Listing 21.17 uses the UsersOnline control to display the number of users currently online (see Figure 21.6).

Figure 21.6. Display number of users online.

Listing 21.17. ShowUsersOnline.aspx

<%@ Page Language="VB" %> <%@ Register TagPrefix="custom" Namespace="myControls" %> <!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>Show UsersOnline</title> </head> <body> <form runat="server"> <div> How many people are online? <br /> <custom:UsersOnline Runat="server" /> </div> </form> </body> </html>

Note

A user is considered online if his username was used in a call to the ValidateUser(), UpdateUser(), or GetUser() method in the last 15 minutes. You can modify the default time interval of 15 minutes by modifying the userIsOnlineTimeWindow attribute of the membership element in the web configuration file.

Several of the methods of the Membership class return one or more MembershipUser objects. The MembershipUser object is used to represent a particular website member. This class supports the following properties:

  • Comment Enables you to associate a comment with the user.

  • CreationDate Enables you to get the date when the user was created.

  • Email Enables you to get or set the user's email address.

  • IsApproved Enables you to get or set whether or not the user is approved and her account is active.

  • IsLockedOut Enables you to get the user's lockout status.

  • IsOnline Enables you to determine whether the user is online.

  • LastActivityDate Enables you to get or set the date of the user's last activity. This date is updated automatically with a call to CreateUser(), ValidateUser(), or GetUser().

  • LastLockoutDate Enables you to get the date that the user was last locked out.

  • LastLoginDate Enables you to get the date that the user last logged in.

  • LastPasswordChangedDate Enables you to get the date that the user last changed her password.

  • PasswordQuestion Enables you to get the user's password question.

  • ProviderName Enables you to retrieve the name of the Membership provider associated with this user.

  • ProviderUserKey Enables you to retrieve a unique key associated with the user. In the case of the SqlMembershipProvider, this is the value of a GUID column.

  • UserName Enables you to get the name of the user.

Notice that the MembershipUser class does not contain a property for the user's password or password answer. This is intentional. If you need to change a user's password, then you need to call a method.

The MembershipUser class supports the following methods:

  • ChangePassword Enables you to change a user's password.

  • ChangePasswordQuestionAndAnswer Enables you to change a user's password question and answer.

  • GetPassword Enables you to get a user's password.

  • ResetPassword Enables you to reset a user's password to a randomly generated password.

  • UnlockUser Enables you to unlock a user account that has been locked out.

Encrypting and Hashing User Passwords

Both of the default Membership providers included in the ASP.NET Framework enable you to store user passwords in three ways:

  • Clear Passwords are stored in clear text.

  • Encrypted Passwords are encrypted before they are stored.

  • Hashed Passwords are not stored. Only the hash values of passwords are stored. (This is the default value.)

You configure how passwords are stored by setting the passwordFormat attribute in the web configuration file. For example, the web configuration file in Listing 21.18 configures the SqlMembershipProvider to store passwords in plain text.

Listing 21.18. Web.Config

<?xml version="1.0"?> <configuration> <system.web> <authentication mode="Forms" /> <membership defaultProvider="MyProvider"> <providers> <add name="MyProvider" type="System.Web.Security.SqlMembershipProvider" passwordFormat="Clear" connectionStringName="LocalSqlServer"/> </providers> </membership> </system.web> </configuration>

The default value of the passwordFormat attribute is Hashed. By default, actual passwords are not stored anywhere. A hash value is generated for a password and the hash value is stored.

Note

A hash algorithm generates a unique value for each input. The distinctive thing about a hash algorithm is that it works in only one direction. You can easily generate a hash value from any value. However, you cannot easily determine the original value from a hash value.

The advantage of storing hash values is that even if your website is compromised by a hacker, the hacker cannot steal anyone's passwords. The disadvantage of using hash values is that you also cannot retrieve user passwords. For example, you cannot use the PasswordRecovery control to email a user his original password.

Instead of hashing passwords, you can encrypt the passwords. The disadvantage of encrypting passwords is that it is more processor intensive than hashing passwords. The advantage of encrypting passwords is that you can retrieve user passwords.

The web configuration file in Listing 21.19 configures the SqlMembershipProvider to encrypt passwords. Notice that the web configuration file includes a machineKey element. You must supply an explicit decryptionKey when encrypting passwords.

Note

For more information on the machineKey element, see the "Using Forms Authentication Across Applications" section, earlier in this chapter.

Listing 21.19. Web.Config

<?xml version="1.0"?> <configuration> <system.web> <authentication mode="Forms" /> <membership defaultProvider="MyProvider"> <providers> <add name="MyProvider" type="System.Web.Security.SqlMembershipProvider" passwordFormat="Encrypted" connectionStringName="LocalSqlServer"/> </providers> </membership> <machineKey decryption="AES" decryptionKey="306C1FA852AB3B0115150DD8BA30821CDFD125538A0C606DACA53DBB3C3E0AD2" /> </system.web> </configuration>

Warning

Make sure that you change the value of the decryptionKey attribute before using the web configuration file in Listing 21.19. You can generate a new decryptionKey with the GenerateKeys.aspx page described in the "Using Forms Authentication Across Applications" section, earlier in this chapter.

Modifying User Password Requirements

By default, passwords are required to contain at least 7 characters and 1 non-alphanumeric character (a character that is not a letter or a number such as *,_, or !). You can set three Membership provider attributes that determine password policy:

  • minRequiredPasswordLength The minimum required password length (the default value is 7).

  • minRequiredNonalphanumericCharacters The minimum number of non-alphanumeric characters (the default value is 1).

  • passwordStrengthRegularExpression The regular expression pattern that a valid password must match (the default value is an empty string).

The minRequiredNonAlphanumericCharacters attribute confuses everyone. Website users are not familiar with the requirement that they must enter a non-alphanumeric character. The web configuration file in Listing 21.20 illustrates how you can disable this requirement when using the SqlMembershipProvider.

Listing 21.20. Web.Config

<?xml version="1.0"?> <configuration> <system.web> <authentication mode="Forms" /> <membership defaultProvider="MyProvider"> <providers> <add name="MyProvider" type="System.Web.Security.SqlMembershipProvider" minRequiredNonalphanumericCharacters="0" connectionStringName="LocalSqlServer"/> </providers> </membership> </system.web> </configuration>

Locking Out Bad Users

By default, if you enter a bad password more than five times within 10 minutes, your account is automatically locked out. In other words, it is disabled.

Also, if you enter the wrong answer for the password answer more than five times in a 10-minute interval, your account is locked out. You get five attempts at your password and five attempts at your password answer. (These two things are tracked independently.)

Two configuration settings control when an account gets locked out:

  • maxInvalidPasswordAttempts The maximum number of bad passwords or bad password answers that you are allowed to enter (default value is 5).

  • passwordAttemptWindow The time interval in minutes in which entering bad passwords or bad password answers results in being locked out.

For example, the web configuration file in Listing 21.21 modifies the default settings to enable you to enter a maximum of three bad passwords or bad password answers in one hour.

Listing 21.21. Web.Config

<?xml version="1.0"?> <configuration> <system.web> <authentication mode="Forms" /> <membership defaultProvider="MyProvider"> <providers> <add name="MyProvider" type="System.Web.Security.SqlMembershipProvider" maxInvalidPasswordAttempts="3" passwordAttemptWindow="60" connectionStringName="LocalSqlServer"/> </providers> </membership> </system.web> </configuration>

After a user has been locked out, you must call the MembershipUser.UnlockUser() method to re-enable the user account. The page in Listing 21.22 enables you to enter a username and remove a lock (see Figure 21.7).

Figure 21.7. Removing a user lock.

Listing 21.22. RemoveLock.aspx

<%@ Page Language="VB" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <script runat="server"> Sub btnRemove_Click(ByVal sender As Object, ByVal e As EventArgs) Dim userToUnlock As MembershipUser = Membership.GetUser(txtUserName.Text) If IsNothing(userToUnlock) Then lblMessage.Text = "User not found!" Else userToUnlock.UnlockUser() lblMessage.Text = "Lock removed!" End If End Sub </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Remove Lock</title> </head> <body> <form runat="server"> <div> <asp:Label Text="User Name:" AssociatedControl Runat="server" /> <asp:TextBox Runat="server" /> <asp:Button Text="Remove Lock" OnClick="btnRemove_Click" Runat="server" /> <br /> <asp:Label EnableViewState="false" Runat="server" /> </div> </form> </body> </html>

Configuring the SQLMembershipProvider

The SqlMembershipProvider is the default Membership provider. Unless otherwise configured, it stores membership information in the local ASPNETDB.mdf Microsoft SQL Server Express database located in your application's App_Data folder. This database is created for you automatically the first time that you use Membership.

If you want to store membership information in some other Microsoft SQL Server database, then you need to perform the following two tasks:

  • Add the necessary database objects to the Microsoft SQL Server database.

  • Configure your application to use the new database.

To complete the first task, you can use the aspnet_regiis command-line tool. This tool is located in the following folder:

\WINDOWS\Microsoft.NET\Framework\v2.0.50727

Note

If you open the SDK Command Prompt, then you don't need to navigate to the Microsoft.NET folder before using the aspnet_regsql tool.

If you execute the aspnet_regsql tool without supplying any parameters, then the ASP.NET SQL Server Setup Wizard appears (see Figure 21.8). You can use this wizard to select a database and install the Membership objects automatically.

Figure 21.8. Using the ASP.NET SQL Setup Wizard.

If you prefer, rather than use the aspnet_reqsql tool, you can execute the following two SQL batch files to install Membership:

\WINDOWS\Microsoft.NET\Framework\v2.0.50727\InstallCommon.sql \WINDOWS\Microsoft.NET\Framework\v2.0.50727\InstallMembership.sql

If you don't want to install the .NET Framework on your database server, then you can execute these SQL batch files.

After you have configured your database to support ASP.NET Membership, you must configure your application to connect to your database when using Membership. The web configuration file in Listing 21.23 connects to a database named MyDatabase located on a server named MyServer.

Listing 21.23. Web.Config

[View full width]

<?xml version="1.0"?> <configuration> <connectionStrings> <add name="MyConnection" connectionString="Data Source=MyServer;Integrated Security=True;Initial Catalog=MyDatabase"/> </connectionStrings> <system.web> <authentication mode="Forms" /> <membership defaultProvider="MyMembershipProvider" > <providers> <add name="MyMembershipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="MyConnection" /> </providers> </membership> </system.web> </configuration>

In Listing 21.23, a new default Membership provider named MyMembershipProvider is configured. The new Membership provider uses a connection string name that has the value MyConnection. The MyConnection connection string is defined in the connectionStrings element near the top of the configuration file. This connection string represents a connection to a database named MyDatabase located on a server named MyServer.

Configuring the ActiveDirectoryMembershipProvider

The other Membership provider included in the ASP.NET Framework is the ActiveDirectoryMembershipProvider. You can use this provider to store user information in Active Directory or ADAM (Active Directory Application Mode).

ADAM is a lightweight version of Active Directory. You can download ADAM from the Microsoft website (www.microsoft.com/adam). ADAM is compatible with both Microsoft Windows Server 2003 and Microsoft Windows XP Professional (Service Pack 1).

If you want to use ASP.NET Membership with ADAM, then you need to complete the following two steps:

1.

Create an ADAM instance and create the required classes.

2.

Configure your application to use the ActiveDirectoryMembershipProvider and connect to the ADAM instance.

The following sections examine each of these steps in turn.

Configuring ADAM

First, you need to set up a new instance of ADAM. After downloading and installing ADAM, follow these steps:

1.

Launch the Active Directory Application Mode Setup Wizard by selecting Create an ADAM Instance from the ADAM program group (see Figure 21.9).

2.

In the Setup Options step, select the option to create a unique instance.

3.

In the Instance Name step, enter the name WebUsersInstance.

4.

In the Ports step, use the default LDAP and SSL port numbers (389 and 636).

5.

In the Application Directory Partition step, create a new directory application partition named O=WebUsersDirectory.

6.

In the File Locations step, use the default data file locations.

7.

In the Service Account Selection step, select Network Service Account.

8.

In the ADAM Administrators step, select Currently Logged on User for the administrator account.

9.

In the Importing LDIF Files step, select MS-AZMan.ldf, MS-InetOrgPerson.ldf, MS-User.ldf, MS-UserProxy.ldf.

Figure 21.9. Creating a new ADAM instance.

After you have completed the preceding steps, a new ADAM instance named WebUsersInstance is created. The next step is to configure an ADAM administrator account. Follow these steps:

Warning

If you are using Windows XP, and you don't have an SSL certificate installed, then you need to perform an additional configuration step. Otherwise, you'll receive an error when you attempt to reset a user password.

By default, you are not allowed to perform password operations over a non-secured connection to an ADAM instance. You can disable this requirement by using the dsmgmt.exe tool included with ADAM. Open the ADAM Tools Command Prompt and type the following series of commands:

1.

Type dsmgmt.

2.

Type ds behavior.

3.

Type connections.

4.

Type connect to server localhost:389.

5.

Type quit.

6.

Type allow passwd op on unsecured connection.

7.

Type quit.

If you don't use an SSL connection, then passwords are transmitted in plain text. Don't do this in the case of a production application.

1.

Open the ADAM ADSI Edit application from the ADAM program group (see Figure 21.10).

2.

Open the Connection Settings dialog box by selecting the menu option Action, Connect To.

3.

In the Connection Settings dialog box, select the option to connect to a node by using a distinguished name and enter the name O=WebUsersDirectory. Click OK.

4.

Expand the new connection and select the O=WebUsersDirectory node.

5.

Select the menu option Action, New, Object.

6.

In the Create Object dialog box, select the organizationalUnit class and name the new class WebUsers.

7.

Select the OU=WebUsers node and select the menu option Action, New, Object.

8.

In the Create Object dialog box, select the user class and name the new class ADAMAdministrator.

9.

Select CN=ADAMAdministrator and select the menu option Action, Reset Password and enter the password secret_.

10.

Select the CN=Roles node and double-click the CN-Administrators node.

11.

Double-click the Member attribute and add the distinguished name for the ADAMAdministrator ADAM account (CN=ADAMAdministrator,OU=WebUsers,O=WebUsersDirectory).

Figure 21.10. Using ADAM ADSI Edit.

After you complete this series of steps, an ADAMAdministrator account is configured. You need to use this account when connecting to the ADAM instance from the ActiveDirectoryMembershipProvider.

Configuring the ActiveDirectoryMembershipProvider

The next step is to configure your application to use the ActiveDirectoryMembership provider. You can use the web configuration file in Listing 21.24.

Listing 21.24. Web.Config

<?xml version="1.0"?> <configuration> <connectionStrings> <add name="ADAMConnection" connectionString="LDAP://localhost:389/OU=WebUsers,O=WebUsersDirectory"/> </connectionStrings> <system.web> <authentication mode="Forms" /> <membership defaultProvider="MyMembershipProvider"> <providers> <add name="MyMembershipProvider" type="System.Web.Security.ActiveDirectoryMembershipProvider" connectionStringName="ADAMConnection" connectionProtection="None" connectionUsername="CN=ADAMAdministrator,OU=WebUsers,O=WebUsersDirectory" connectionPassword="secret_" enableSearchMethods="true" /> </providers> </membership> </system.web> </configuration>

The web configuration file in Listing 21.24 configures a new default Membership provider named MyMembershipProvider. This provider is an instance of the ActiveDirectoryMembershipProvider.

Several of the attributes used with the ActiveDirectoryMembershipProvider require additional explanation. The connectionStringName attribute points to the connection string defined in the connectionStrings section. This connection string connects to a local ADAM instance that listens on port 389.

Notice that the connectionProtection attribute is set to the value None. If you don't modify this attribute, then you are required to use an SSL connection. If you do use an SSL connection, you need to change the port used in the connection string (typically port 636).

The connectionUsername and connectionPassword attributes use the ADAMAdministrator account that you configured in the previous section. When you don't use an SSL connection, you must provide both a connectionUsername and connectionPassword attribute.

Finally, notice that the provider declaration includes an enableSearchMethods attribute. If you want to be able to configure users by using the Web Site Administration Tool, then you must include this attribute.

The ActiveDirectoryMembershipProvider class supports several attributes specific to working with Active Directory:

  • connectionStringName Enables you to specify the name of the connection to the Active Directory Server in the connectionStrings section.

  • connectionUsername Enables you to specify the Active Directory account used to connect to Active Directory.

  • connectionPassword Enables you to specify the Active Directory password used to connect to Active Directory.

  • connectionProtection Enables you to specify whether or not the connection is encrypted. Possible values are None and Secure.

  • enableSearchMethods Enables the ActiveDirectoryMembershipProvider class to use additional methods. You must enable this attribute when using the Web Site Administration Tool.

  • attributeMapPasswordQuestion Enables you to map the Membership security question to an Active Directory attribute.

  • attributeMapPasswordAnswer Enables you to map the Membership security answer to an Active Directory attribute.

  • attributeMapFailedPasswordAnswerCount Enables you to map the Membership MaxInvalidPasswordAttempts property to an Active Directory attribute.

  • attributeMapFailedPasswordAnswerTime Enables you to map the Membership PasswordAttemptWindow property to an Active Directory attribute.

  • attributeMapFailedPasswordAnswerLockoutTime Enables you to map the Membership PasswordAnswerAttemptLockoutDuration property to an Active Directory attribute.

After you finish these configuration steps, you can use the ActiveDirectoryMembershipProvider in precisely the same way that you can use the SqlMembershipProvider. When you use the Login control, users are validated against Active Directory. When you use the CreateUserWizard control, new users are created in Active Directory.

Creating a Custom Membership Provider

Because ASP.NET Membership uses the provider model, you can easily extend ASP.NET membership by creating a custom Membership provider. There are two main situations in which you might need to create a custom Membership provider.

First, imagine that you have an existing ASP.NET 1.x or ASP classic application. You are currently storing membership information in your own custom set of database tables. Furthermore, your table schemas don't easily map to the table schemas used by the SqlMembershipProvider.

In this situation, it makes sense to create a custom Membership provider that reflects your existing database schema. If you create a custom Membership provider, you can use your existing database tables with ASP.NET Membership.

Second, imagine that you need to store membership information in a data store other than Microsoft SQL Server or Active Directory. For example, your organization might be committed to Oracle or DB2. In that case, you need to create a custom Membership provider to work with the custom data store.

In this section, we create a simple custom Membership provider: an XmlMembershipProvider that stores membership information in an XML file.

Unfortunately, the code for the XmlMembershipProvider is too long to place here. The code is included on the CD that accompanies this book in a file named XmlMembershipProvider.vb, located in the App_Code folder.

The XmlMembershipProvider class inherits from the abstract MembershipProvider class. This class has over 25 properties and methods that you are required to implement.

For example, you are required to implement the ValidateUser() method. The Login control calls this method when it validates a username and password.

You also are required to implement the CreateUser() method. This method is called by the CreateUserWizard control when a new user is created.

The web configuration file used to set up the XmlMembershipProvider is contained in Listing 21.25.

Listing 21.25. Web.Config

<?xml version="1.0"?> <configuration> <system.web> <authentication mode="Forms" /> <membership defaultProvider="MyMembershipProvider"> <providers> <add name="MyMembershipProvider" type="AspNetUnleashed.XmlMembershipProvider" dataFile="~/App_Data/Membership.xml" requiresQuestionAndAnswer="false" enablePasswordRetrieval="true" enablePasswordReset="true" passwordFormat="Clear" /> </providers> </membership> </system.web> </configuration>

Notice that the XmlMembershipProvider supports a number of attributes. For example, it supports a passwordFormat attribute that enables you to specify whether passwords are stored as hash values or as plain text. (It does not support encrypted passwords.)

The XmlMembershipProvider stores membership information in an XML file named Membership.xml, located in the App_Data folder. If you want, you can add users to the file by hand. Alternatively, you can use the CreateUserWizard control or the Web Site Administration Tool to create new users.

A sample of the Membership.xml file is contained in Listing 21.26.

Listing 21.26. App_Data\Membership.xml

<credentials> <user name="Steve" password="secret" email="steve@somewhere.com" /> <user name="Andrew" password="secret" email="andrew@somewhere.com" /> </credentials>

The sample code folder on the CD includes a Register.aspx, Login.aspx, and ChangePassword.aspx page. You can use these pages to try out different features of the XmlMembershipProvider.

Warning

Dynamic XPath queries are open to XPath Injection Attacks in the same way that dynamic SQL queries are open to SQL Injection Attacks. When writing the XmlMembershipProvider class, I avoided using methods such as the SelectSingleNode() method to avoid XPath Injection Attack issues, even though using this method would result in leaner and faster code. Sometimes, it is better to be safe than fast.

Категории