MembershipProvider


This abstract base class defines the interface for accessing a user account store through the membership feature in ASP.NET. Refer to Listing 5-5 and familiarize yourself with this interface, as this is a great way to get a quick introduction to the features that membership offers.

Listing 5-5. MembershipProvider

public abstract class MembershipProvider : ProviderBase {   // user management   public abstract MembershipUser CreateUser(string username,                                             string password,                                             string email,                                             string passwordQuestion,                                             string passwordAnswer,                                             bool isApproved,                                             object providerUserKey,                                         out MembershipCreateStatus s);   public abstract void UpdateUser(MembershipUser user);   public abstract bool DeleteUser(string username,                                   bool deleteAllRelatedData);   public abstract bool UnlockUser(string userName);   // user identification and authentication   public abstract string GetUserNameByEmail(string email);   public abstract bool ValidateUser(string username,                                     string password);   // user credential management   public abstract string GetPassword(string username,                                      string answer);   public abstract string ResetPassword(string username,                                        string answer);   public abstract bool ChangePassword(string username,                                       string oldPassword,                                       string newPassword);   public abstract bool ChangePasswordQuestionAndAnswer(                           string username,                           string password,                           string newQuestion,                           string newAnswer);   public event MembershipValidatePasswordEventHandler                           ValidatingPassword;   // finding users (some details omitted for brevity)   public abstract MembershipUserCollection FindUsersByEmail(...);   public abstract MembershipUserCollection FindUsersByName(...);   public abstract MembershipUserCollection GetAllUsers(...);   public abstract int GetNumberOfUsersOnline();   public abstract MembershipUser GetUser(string name, bool isOnline);   public abstract MembershipUser GetUser(object key, bool isOnline);   // configuration properties   public abstract string ApplicationName { get; set; }   public abstract bool EnablePasswordReset { get; }   public abstract bool EnablePasswordRetrieval { get; }   public abstract int MaxInvalidPasswordAttempts { get; }   public abstract int MinRequiredNonAlphanumericCharacters { get; }   public abstract int MinRequiredPasswordLength { get; }   public abstract int PasswordAttemptWindow { get; }   public abstract MembershipPasswordFormat PasswordFormat { get; }   public abstract string PasswordStrengthRegularExpression { get; }   public abstract bool RequiresQuestionAndAnswer { get; }   public abstract bool RequiresUniqueEmail { get; } } 

The CreateUser method gives you a good idea of the type of user data tracked by membership. Each user has a name and password and an e-mail address. To allow users to reset their own passwords, there is also an optional question and answer that can be used as a secondary form of authentication for the user. Each user has a flag that indicates whether they are approved to log in. To ban a user, you can clear this flag and they will no longer be allowed to log in (the AD provider is serious about this; it disables the user account if you do this). If you ever call CreateUser directly, pass a null argument to indicate a missing parameter (providerUserKey, password question and answer, etc.), as opposed to an empty string, which the provider may interpret as a value you want it to use.

UpdateUser and DeleteUser are pretty self-explanatory, except perhaps for the deleteRelatedData argument. If you pass true, SqlMembershipProvider deletes all records related to the user, including role memberships and profile, and so on. If you pass false, the only thing that is deleted is the membership record, which contains items like the user's e-mail address and password verifier, making it impossible for that user to log in again.

The presence of the UnlockUser method indicates that accounts can be locked out after a certain number of failed logon attempts. Account lockout policy depends on the provider. Under the AD provider, the number of failed password attempts is controlled by password policy in AD, while the SQL provider relies on the maxInvalidPasswordAttempts attribute specified in web.config.

ValidateUser is perhaps the most important method: this is how the login control (or even your own code) attempts to log on a user, supplying the user name and password. While the AD provider forwards this request on to a directory service, the SQL provider must actually perform the password validation itself against a password verifier stored in the membership database. We'll devote a section to password verification later in this chapter, as it's an important configuration choice you have to make if you're going to use the SQL provider. If a valid password is supplied, the current time is recorded for the user account, which helps the membership system make an educated guess as to which members are actively online.[7] On the other hand, if ValidateUser is called with an invalid password for a particular user in rapid succession, the user's account may be locked out, a feature we'll discuss in more detail. After all this takes place, ValidateUser updates performance counters and fires a health monitoring event, which helps you track the number of successful versus failed login attempts for your Web application.

[7] You can tweak this with the userIsOnlineTimeWindow attribute in the <membership> config element. The default is 10 minutes.

Given that a user is much more likely to remember her e-mail address than to remember the user account name she chose to use at your Web site, the GetUserNameByEmail method can really come in handy. If the user can't figure out her user name, you can look it up for her based on her e-mail address. You could use this in conjunction with the events fired by the login control to allow the user to enter either a user name or an e-mail address in order to log in.

You've now seen enough Membership plumbing to understand the login control, so let's digress for a moment to examine it in more detail.

The Login Control

The login control is very useful, as it allows you to crank out a login page with a single line of markup, as you saw at the beginning of this chapter, but you can use it other places as well. Imagine dropping a login control onto your master page that would let anonymous users browse your Web site and log in if they want more personalized service. Many Web sites work this way.

The login control is infinitely customizable. Using the designer, you can autoformat an instance of the control to give it a look you like, or you can centralize the look of all your login controls with a skin (see Chapter 2). You can also customize each bit of text shown, orient the control so that it lies horizontally instead of vertically, and use images instead of buttons. Figure 5-6 shows the default login control above the one that we customized by tweaking some of these settings and adding an image to replace the Log In button. You can find the code for this page in the LoginControlCustomization sample available for download with this book.

Figure 5-6. Customizing the login control


Besides controlling the look and feel of this control, you'll need to make some important decisions about what features you want to expose to your users. The "Remember me next time" checkbox is convenient for users, but it also persists the forms login cookie on the user's hard drive. In ASP.NET 1.x this persistent cookie was set to last for 50 years! Fortunately in ASP.NET 2.0, the cookie expires based on the timeout you configure for Forms authentication.[8] Keep in mind that an attacker who steals a forms login cookie can replay it at his leisure and impersonate the legitimate user from whom it was stolen, and allowing cookies to be stored on the user's hard drive may increase the feasibility of this attack. If this is a concern, you can effectively disable this feature in the customized control by setting the control's DisplayRememberMe to false.

[8] If you're upgrading to ASP.NET 2.0, this may surprise you: by default, users who check the "Remember me" box will only be remembered for 30 minutes after they stop using your Web application. You can change this by adjusting the timeout attribute on the <forms> element in your web.config file.

The login control will optionally display links to other membership-related pages for performing tasks such as creating users, recovering passwords, or getting help. And if all else fails, you can choose the "Convert to Template" task from the Designer view, which will let you lay out the control however you like. In this mode, you can reposition any of the existing controls, or even add and remove controls if you like.

The login control fires a sequence of events when the user presses the Login button. The first is the LoggingIn event, where you can preprocess the request and choose to allow or cancel the login before it's even attempted. The next event fired, Authenticate, is the core event for this control; it contacts the Membership provider and authenticates the user. If you handle this event, the default processing won't happen, so you'll have to authenticate the user yourself (which isn't hard with a Membership provider using its ValidateUser method). If your handler returns an event arg indicating success, the control sets up the Forms authentication cookie and then fires the LoggedIn event. Otherwise it fires the LoginError event, which by default informs the user of the login failure.

User Account Lockout: Blessing or Curse?

SqlMembershipProvider has a user account lockout feature that is designed to thwart password guessing attacks. Here's how it works: for a given user account, the first time ValidateUser is given an invalid password, a counter in the user's membership record (FailedPasswordAttemptCount) is bumped up, and the time of this first failed login is recorded (FailedPasswordAttemptWindowStart). If this happens five times in rapid succession (within ten minutes by default), the user's account will be locked out. You can modify these thresholds in web.config via the maxInvalidPasswordAttempts and passwordAttemptWindow attributes on the provider.

ActiveDirectoryMembershipProvider indirectly provides the same service, because directory services typically provide optional account lockout features. For example, if you're using AD as your user store, you can use the account lockout features in AD to lock user accounts after a certain number of failed login attempts. In both the SQL and AD provider cases, an administrator must intervene and unlock the user's account before she will be allowed to log in again. This is when the UnlockUser method comes in handy.

Before you get too excited about account lockout, consider the dark side. While it's hard for an attacker to guess a correct password for a user, it's trivial to guess an incorrect password. If I don't like Alice, and I want to make sure she can't log in to your Web site, all I have to do is use an incorrect password in several login attempts in rapid succession. And this attack can be automated. Imagine the pain you'd be in if an attacker learned the names of all of your users, and used that in a script that locked them all out each evening!

You can effectively turn off account lockout if you're worried about this. Just change the configuration to increase those threshold values to very large numbers or turn off the feature in your directory service.[9] Unfortunately this also makes you more vulnerable to password guessing attacks, because there seems to be no delay built into the SqlMembershipProvider. I wrote a little program that turned off the account lockout feature and then dropped it into a loop calling ValidateUser with a known bad password. On my box at home, I was able to make about 530 guesses each second. Obviously, with network latency a remote attacker wouldn't be able to go this fast, but given a dictionary of commonly used passwords, it wouldn't take long to crack a weak password.

[9] If you're using Active Directory, you can turn off account lockout in security policy. The setting, called Account lockout threshold, is found under Security Settings | Account Policies | Account Lockout Policy. By setting this value to zero, you effectively turn off account lockout. But before making this change, I'd recommend reading Chapter 11 of Jesper Johansson and Steve Riley's excellent book Protect Your Windows Network to help you understand more about problems with passwords and account lockout.

A more useful feature might be a delay, as you see when you try to interactively log in to Windows. If you supply around five bad passwords to the interactive Windows login screen, you'll have to wait a little bit before you can try again. The system simply slows you down every four or five tries. A delay like this can significantly reduce the effectiveness of a brute force password guessing attack. The built-in login control and SqlMembershipProvider don't support this, so you'd have to write a bit of code in the login control or build a custom provider if you wanted to add this feature. But it'd be a worthwhile effort.

Password Complexity Policy

Ultimately the best protection against password guessing attacks is to require strong passwords. There are several ways to enforce this password policy with the membership system. The first is through the configuration of your Membership provider. You can modify the following provider attributes to provide some general constraints on password complexity.

  • minRequiredPasswordLength (the default is 7)

  • minRequiredNonAlphanumericCharacters (the default is 1)

There's also a passwordStrengthRegularExpression attribute if you're comfortable using regular expressions. You can find an example of a regular expression that controls password length and requires the use of upper and lower alphanumeric characters and numeric digits in the patterns & practices guidance document How to Protect Forms Authentication in ASP.NET 2.0.[10]

[10] See http://msdn.microsoft.com/library/en-us/dnpag2/html/PAGHT000012.asp.

Each time a password is updated via the membership service, the ValidatingPassword event is fired. Don't let the name confuse you: this event isn't fired when a user is authenticated, only when his password is being changed. If you handle this event, you will be given the proposed new password, and you can decide whether or not it is acceptable. You could plug in code to do your own dictionary attack against the password, or add in a password history database to ensure that users aren't reusing old passwords.

If you are using the AD provider, your directory service's password policy will act as a yet another complexity check. Passwords will only be accepted if they pass both the membership password complexity constraints and the directory service's constraints.

The MembershipProvider class includes several methods that help to automate user credential management, which are shown in Listing 5-5. Not all of these functions will be available to you; it depends on the provider you use and how you configure that provider. For example, the AD provider will throw a NotSupportedException if GetPassword is called. Most directory services don't support retrieval of user passwords because the cleartext password is simply not available most of the time. For example, unless you explicitly mark an account in Active Directory as storing its password reversibly encrypted, AD will only store a hash of the password. Which brings us to another critical topic: password format.




Essential ASP. NET 2.0
Essential ASP.NET 2.0
ISBN: 0321237706
EAN: 2147483647
Year: 2006
Pages: 104

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