Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Asp Net 2.0 Security Membership And Role Management

.pdf
Скачиваний:
55
Добавлен:
17.08.2013
Размер:
12.33 Mб
Скачать

Chapter 10

Especially for the SqlMembershipProvider, date-time values are usually created and compared on the web server, and then transmitted and stored on a database server. In any web farm with more than one server, this means that no single master server is responsible for generating date-time values. You could definitely end up with one web server logging a failed login attempt (and hence updating the date-time related failure data) and a different server loading this information during the course of processing a second login attempt. Excessive amounts of clock skew across a web farm will lead to incorrect time calculations being made in this type of scenario. A few seconds of time skew isn’t going to be noticeable, but if your servers are minutes apart, you will probably see intermittent problems with date-time-related functionality.

If you plan on writing custom providers for the Membership feature, you should keep the “UTC-ness” of the feature in mind. If at all possible custom providers should follow the same behavior as the built-in providers, and store all date-time information internally as UTC date-times.

The MembershipProvider Base Class

The central part of the Membership feature is its use of providers that derive from System.Web.Security.MembershipProvider. Out of the box, the Framework ships with two implementations of this class: SqlMembershipProvider and ActiveDirectoryMembershipProvider. Both of these providers are discussed in more detail in succeeding chapters. Because the Membership feature allows you to configure any type of provider, you can also write your own custom implementations of this class.

The base class definition that all providers must adhere to is shown below. The class definition falls into three major areas: abstract properties, abstract and protected methods, and a small number of eventrelated definitions.

public abstract class MembershipProvider : ProviderBase

{

//Properties

public abstract bool EnablePasswordRetrieval { get; } public abstract bool EnablePasswordReset { get; } public abstract bool RequiresQuestionAndAnswer { get; } public abstract string ApplicationName { get; set; } public abstract int MaxInvalidPasswordAttempts { get; } public abstract int PasswordAttemptWindow { get; } public abstract bool RequiresUniqueEmail { get; }

public abstract MembershipPasswordFormat PasswordFormat { get; } public abstract int MinRequiredPasswordLength { get; }

public abstract int MinRequiredNonAlphanumericCharacters { get; } public abstract string PasswordStrengthRegularExpression { get; }

//Public Methods

public abstract MembershipUser CreateUser( string username,

string

password, string email, string passwordQuestion,

string

passwordAnswer, bool

isApproved, object providerUserKey,

out

MembershipCreateStatus

status )

public abstract bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)

public abstract string GetPassword(string username, string answer)

382

Membership

public abstract bool ChangePassword(string username, string oldPassword, string newPassword)

public abstract string ResetPassword(string username, string answer) public abstract void UpdateUser(MembershipUser user)

public abstract bool ValidateUser(string username, string password) public abstract bool UnlockUser( string userName )

public abstract MembershipUser GetUser( object providerUserKey, bool userIsOnline )

public abstract MembershipUser GetUser(string username, bool userIsOnline) public abstract string GetUserNameByEmail(string email)

public abstract bool DeleteUser(string username, bool deleteAllRelatedData) public abstract MembershipUserCollection GetAllUsers(int pageIndex,

int pageSize, out int totalRecords)

public abstract int GetNumberOfUsersOnline()

public abstract MembershipUserCollection FindUsersByName( string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)

public abstract MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)

//Protected helper methods

protected virtual byte[] EncryptPassword( byte[] password ) protected virtual byte[] DecryptPassword( byte[] encodedPassword )

//Events and event related methods

public event MembershipValidatePasswordEventHandler ValidatingPassword protected virtual void OnValidatingPassword( ValidatePasswordEventArgs e )

}

If you are thinking about writing a custom provider, the extensive abstract class definition may seem a bit intimidating at first. An important point to keep in mind though is that not only is the Membership feature pluggable by way of providers — the breadth of functionality you choose to implement in a provider is also up to you. Although the SQL and AD based providers implement most of the functionality defined by the abstract class (the SQL provider implements 100% of it and the AD provider implements about 95% of it), it is a perfectly reasonable design decision to implement only the slice of provider functionality that you care about. For example, you may not care about exposing search functionality from your provider, in which case you could ignore many of the Get* and Find* methods.

The way to think about the available functionality exposed by a provider is to break it down into the different areas described in the next few sections. If there are broad pieces of functionality you don’t care about, you can just stub out the requisite properties and methods for that functionality in your custom provider by throwing a NotSupportedException.

Basic Configuration

A portion of the MembershipProvider class signature deals directly with configuration information that is usually expected to be available from any custom provider.

383

Chapter 10

All providers should at least implement the getter for the ApplicationName property. The concept of separating data by application name is so common to many of the new provider-based features in ASP.NET 2.0 that the getter should always be implemented. If it turns out that you are mapping Membership to a data store that doesn’t really have the concept of an “application” (for example, the AD provider doesn’t support the concept of an application but it does implement the getter), you can have the setter throw a NotSupportedException. Internally, your custom provider can just ignore the application name that it loaded from configuration.

User Creation and User Updates

Most of the functionality on a MembershipProvider isn’t of much use unless users are created in the first place. You have two approaches to this:

You can write a full-featured provider that implements the create-, delete-, and update-related methods.

You can stub out all of the create-, delete-, and update-related methods if you have some other mechanism for populating the data store. For example, your provider may only expose the ability to validate a username-password pair. The actual user accounts may be created through some other mechanism. In this scenario, your custom provider could just choose not to implement the ability to create and update users.

The properties related to user creation and user updates mostly deal with the user’s password.

MinRequiredPasswordLength — On one hand, if a provider supports enforcing password strengths, it should return the minimum length of passwords allowed when using the provider. On the other hand, if a provider does not enforce any kind of password strength requirements, it should just return either zero or one from this property. If a provider doesn’t care about password lengths, then it can return the number one as a reasonable default. The CreateUserWizard and the ChangePassword controls both use this property when outputting error information. However, neither of the controls automatically generates any type of validators based on this property — they just use the property value for outputting default error information if an invalid password was entered into the controls.

MinRequiredNonAlphanumericCharacters — A provider that enforces password strength rules can choose to also require a minimum number of nonalphanumberic characters in passwords. A custom provider that either does not enforce password strength or does not have the additional requirement around nonalphanumeric characters should just return zero from this property. The CreateUserWizard and the ChangePassword controls both use this property when outputting error information. However, neither of the controls automatically generates any type of validators based on this property — they just use the property value for outputting default error information if an invalid password was entered into the controls.

PasswordStrengthRegularExpression — Because some developers have more complex password rules, they may use regular expressions instead of (or in addition to) the previous constraints. A provider that supports custom regular expressions should return the regular expression that was configured via this property. If a provider does not support enforcing password strength via a custom regular expression, it should just return an empty string from this property. You could argue that throwing a NotSupportedException would make sense, but returning a hard-coded empty string is just as effective and doesn’t result in an unexpected exception when reading the property. Note that the CreateUserWizard and ChangePassword

384

Membership

controls don’t make use of this property. Both of these controls also support specifying a regular expression for password validation — however the regular expression on these controls is intended for use in a client-side regular expression validator (that is, a regular expression that works in JavaScript) and as a result they do not use the value returned from this property.

ValidatingPassword — This is a public event defined on the base MembershipProvider class. Because it is not defined as virtual, it’s possible for developers to register custom password validation handlers even though a custom provider may not support extensible password validation and, thus, will never fire this event. For now, the best way to inform developers that a provider doesn’t support extensible password validation is to document the limitation. There is a related protected virtual method that providers use (OnValidatingPassword) to fire the event.

RequiresUniqueEmail — If you want to ensure that any users created with your custom membership provider have a unique email return true from this property. If you don’t care about email uniqueness return false from this property. The CreateUser control in the Login controls will add a validator that requires a valid email address in the event a provider returns true from this property.

The methods related to user creation and updates deal with both the MembershipUser object as well changing just the user’s password.

CreateUser — If your provider supports creating users then you would implement this method. However, if you have some other mechanism for creating users you should just throw a NotSupportedException from this method. If your provider requires unique email addresses (based on the requiresUniqueEmail configuration attribute), then its implementation should perform the necessary validations to enforce this. If your provider doesn’t support explicitly defining the data-store-specific primary key with the providerUserKey parameter, it should throw a NotSupportedException in the event that a non-null value is supplied for this parameter. For other parameters, your provider should perform validations based on the password strength enforcement properties and password question and answer configuration properties. If a provider supports extensible password validation routines, it should raise the ValidatingPassword event as well. This allows developers to provide custom password validation — with the most likely place to do this being global.asax. Because the CreateUser method returns a status parameter of type MembershipCreateStatus, you can set the status to one of the error codes (that is, something other than MembershipCreateStatus.Success) in the event that a validation check fails. Normally, the CreateUser method should not return an exception if a parameter validation fails because there is an extensive set of status codes that can be returned from this method. A NotSupportedException should only be thrown for cases where a parameter is supplied but the provider doesn’t support the functionality that would make use of this parameter (that is, attempting to set the providerUserKey or supplying questions and answers when the provider can’t store these values or make use of them). The CreateUserWizard internally calls this method on the provider configured for use with the control.

DeleteUser — The companion to the CreateUser method. If a custom provider supports creating users, it likely also supports deleting users. Depending on how a custom provider is written, other features may depend on the users created with the provider. For example, the SqlMembershipProvider uses a database schema that integrates with other features such as Role Manager. If this is the case for a custom provider, it should support the ability to perform a “clean” delete that can remove related data from other features prior to deleting the membership user data. As with CreateUser, if a provider doesn’t support user deletion it should just throw a NotSupportedException from this method.

385

Chapter 10

UpdateUser — After a user is created there is a subset of data on MembershipUser that is updatable. If a custom provider supports updating any user information (Email, Comment,

IsApproved, LastLoginDate, and LastActivityDate), the provider should implement this method. A custom provider can choose to only allow a subset of these properties to be updatable. If email addresses can be updated, a custom provider should enforce the uniqueness of the new value based on the requiresUniqueEmail configuration attribute. The best way to enforce this is by creating a derived MembershipUser class that goes hand in hand with the custom provider. The custom MembershipUser class should throw NotSupportExceptions from the property setters for properties that are not updatable. In this way, you prevent a developer from updating property data that you don’t want to be changed via the provider. The custom provider should also ignore these properties and not use them when issuing a user update. Additionally, a

custom provider that uses a derived MembershipUser type should ensure that the derived MembershipUser class is always passed as a parameter to the UpdateUser method — if some other type is used (for example, the base MembershipUser type), the provider should throw an ArgumentException to make it clear to developers that only the derived MembershipUser type is allowed. This is the general approach used by the ActiveDirectoryMembershipProvider. This provider has a related MembershipUser-derived class that does not allow updates to LastLoginDate or LastActivityDate; it prevents updates to these properties by throwing a

NotSupportedException from these properties on the ActiveDirectoryMembershipUser class. However, the AD-based provider skips some performance optimizations in its update method internally if the wrong MembershipUser type is passed to it. I recommend throwing an ArgumentException instead for custom providers because it makes it clearer that there is a specific MembershipUser-derived type that must be used. Of course, if your provider doesn’t support updating any user data, it should just throw a NotSupportedException instead.

ChangePassword — If your provider supports creating users, you should support the ability for users to at least change their passwords via this method. Your provider should perform validations based on the password strength enforcement properties if your provider supports any type of strength enforcement. Furthermore, if a provider supports extensible password validation routines, it should raise the ValidatingPassword event as well. Because a user’s old password is required to change the password, if a provider keeps track of bad passwords, it should include tracking logic in this method that keeps track of bad password attempts and locks out users as necessary. On one hand, users who have already been locked out should never be allowed to change their password. On the other hand, if you create users through some other mechanism, it is possible that you also have a separate process for allowing users to update their passwords, in which case you should just throw a NotSupportedException. The ChangePassword control in the Login controls calls this method on the provider associated with the control.

OnValidatingPassword — This protected virtual method is defined on the base MembershipProvider class and should be used by custom providers to raise the password validation event from the CreateUser, ChangePassword, and ResetPassword methods. If the event argument for this event is returned with an exception object, the provider should throw the returned exception rather than continuing. If instead the returned event argument just has the Cancel property set to true, a custom provider should throw a ProviderException stating that the password validation failed. If a custom provider doesn’t allow for custom password validation logic to be registered by way of the ValidatingPassword event, there is no great way to communicate this to developers other than through documentation. Unfortunately, the internal property that holds the event delegates for this event is not accessible, so a custom provider has no way to check whether or not events have been registered for it.

386

Membership

Retrieving Data for a Single User

The provider signature supports a number of methods for retrieving single user objects and sets of user data. If a custom provider supports more than just the ValidateUser method, it should at least support the ability to fetch a single MembershipUser instance for a given user.

GetUser — There are two GetUser overloads: one that retrieves users by name and one that retrieves users by way of a data store specific primary key. At a minimum, a custom provider that supports retrieving users should support fetching a MembershipUser by username. This is probably the most common approach for many developers because the username is available off of the HttpContext after a user logs in. If you don’t want to support the concept of retrieving a user with a ProviderUserKey, you can throw a NotSupportedException from this overload. The ChangePassword and PasswordRecovery controls internally call the GetUser overload that accepts a username.

GetUserNameByEmail — If your provider supports storing email addresses for users, it should support the ability to retrieve users by way of their email address. Of course, requiring unique email addresses is pretty much a requirement if you want this method to return any sensible data. Although a provider could allow storing users with duplicate email addresses, calling this method will result in ambiguous data because it can only return a single username. If there are duplicates, a custom provider can either return the first matching username, or it can throw some kind of exception. The general convention though is to return the first matching username

if unique emails are not required and to throw a ProviderException if unique emails are required and more than one matching user record was found. If a provider does not need to support email-based retrieval, it should just throw a NotSupportedException instead.

Retrieving and Searching for Multiple Users

The ability to search for and retrieve multiple users is considered to be more of an administrative task than a normal runtime task. Administrative applications have the most need for the ability to search for users and return arbitrary sets of users. There are no provider properties on MembershipProvider- related to this functionality, though custom providers may have provider-specific configuration properties that deal with search functionality. For example, the ActiveDirectoryMembershipProvider has configuration properties that control how search related methods work. There are number of searchrelated methods though that provider implementers can choose to write.

GetAllUsers — As the name implies, a provider should return all users from the underlying data store. This method is mostly useful for small numbers of users (the low hundreds at most), because for any large quantity of user records, retrieving every possible user is ungainly. The method on the provider class includes parameters to support paging. However, paging can sometimes be difficult to implement, especially for data stores that don’t natively expose any concept of paged results. If your provider doesn’t support paging, it can just ignore the pageIndex and pageSize parameters; there isn’t really any good way to communicate the existence or lack of paging based on this method’s parameter signature. The ASP.NET configuration tool that is available from inside of the Visual Studio environment makes use of this method. If your provider doesn’t support this type of search functionality, throw a NotSupportedException.

FindUsersByName — A filtered search method that can retrieve a set of users based on username. As with GetAllUsers some provider implementers will be able to support paging semantics, while other custom providers will need to ignore the paging-related parameters. Another aspect

387

Chapter 10

of this search method is support for wildcard characters in the usernameToMatch parameter: You will need to document the level of support a custom provider has for wildcard characters. The general expectation is that if the underlying data store (that is, SQL Server) supports wildcards in its native query language, the provider should allow the same set of wildcard characters in the usernameToMatch parameter. The ASP.NET configuration tool that is available from inside of the Visual Studio environment makes use of this method. If your provider doesn’t support this type of search functionality, throw a NotSupportedException.

FindUsersByEmail — This method has the same functionality and guidance as FindUsersByName with the one difference being that it instead supports searching by email address.

Validating User Credentials

When you boil the Membership feature down to its basics, validating passwords is at its core. All other areas of functionality described in this section are pretty much optional; there are other ways that you can support functionality like user creation or searching for users. Without the ability to validate user credentials, though, it would be sort of pointless to write a MembershipProvider. The basic support expected from all MembershipProviders is the ability to validate a username-password pair.

More advanced, and thus optional, functionality allows for tracking bad password attempts and bad password answer attempts. If certain configurable thresholds are met or exceeded a provider should incorporate the ability to lock out user accounts and then subsequently unlock these accounts. If a provider does support tracking bad password and bad password answer attempts, it needs to keep track of this whenever ValidateUser, ChangePassword, ChangePasswordQuestionAndAnswer, ResetPassword, and GetPassword are called. Each of these methods involves a password or a password answer to work properly, although the password answer functionality in ResetPassword and GetPassword is also optional (see the next section on self-service password resets and retrieval). Furthermore, in each of these methods if the correct password or password answer is supplied, then a custom provider should reset its internal tracking counters (either password counters or password answer counters) to reflect this fact. In the next chapter, on SqlMembershipProvider, you will see how the SQL provider handles these types of counters in various MembershipProvider methods.

The properties related to validating user passwords are:

MaxInvalidPasswordAttempts — For more secure providers that support tracking bad passwords (and also bad password answers if they support question-and-answer-based password resets or password retrieval), this setting indicates the maximum number of bad password attempts. If a provider supports tracking bad password answers, this configuration setting is also intended to be used as the maximum number of allowable bad password answers. Although the MembershipProvider could have specified two different properties for tracking bad passwords versus bad password answers, the decision was made to support the same upper limit for both pieces of data. There is always a debate over exactly what “maximum” means when tracking bad attempts; some folks would choose maximum to mean a threshold that can be reached but not exceeded. A reasonable case can be instead be made that this type of limit should instead be triggered only when it is exceeded. Realistically, either approach is valid; the ASP.NET providers consider the maximum number of attempts to have occurred when internal tracking counters exactly equal the value of this configuration setting. This means that if this property is set to five, then when the fifth bad password is supplied something happens — that is, the user account is locked out. Custom provider implementers may choose to be slightly different and instead carry

388

Membership

out some action on the sixth attempt. The main thing is to communicate clearly to folks exactly how this property triggers account lockouts and other behavior. If a custom provider doesn’t support any type of bad password or bad password answer tracking, it should return an appropriately large value instead — Int32.MaxValue for example. Custom providers should avoid throwing an exception because developers may want to use administrative UI that lists all providers configured on a system along with their current configuration settings based on the MembershipProvider properties. Returning a very large value gets across the point that the provider doesn’t enforce anything without causing the administrative UI to blow up with an unexpected exception.

PasswordAttemptWindow — If a provider supports tracking bad passwords or bad password answer attempts, there usually needs to be some finite time window during which the provider actively keeps track of bad attempts. The value returned from this property indicates the length of time during which a provider would consider successive failed attempts to be additive; for example, the provider would increment internal tracking counters that are compared against MaxInvalidPasswordAttempts. The specifics of how a provider deals with the password attempt window over time are considered provider-specific. It is up to the provider implementer to document exactly how the PasswordAttemptWindow interacts with the value for MaxInvalidPasswordAttempts. If a provider doesn’t support the concept of tracking bad attempts, it can instead return a dummy value such as zero from this property rather than throwing an exception. A return value of zero implies that the provider considers each new failed attempt as an isolated event unrelated to prior failed attempts

There are only two methods for credential validation, with ValidateUser being the method that most developers expect to be implemented by all providers.

ValidateUser — If there is one core method that “is” the Membership feature, this is it. Any custom provider will be expected to support this property. After a successful login, the user’s LastLoginDate should be updated. Login controls such as the Login control and the CreateUserWizard depend on this method. Providers that support tracking bad password attempts should increment tracking counters in this method and lock out user accounts as necessary. In general, if a user account is already locked out, ValidateUser should always return false. Similarly, if a custom provider supports the concept of approving a user prior to allowing the user to log on to a site, the provider should also return false if the user’s IsApproved flag is set to false.

UnlockUser — This is an optional method for providers that are able to lockout user accounts after an excessive number of bad passwords or bad password answers. If a custom provider supports this concept, then there needs to be a way to unlock user accounts. There are two general approaches to this. A provider can internally support the concept of auto-unlocking user accounts. Although auto-unlocking is not explicitly supported by the Membership feature, there isn’t anything to prevent a custom provider implementer from building this type of logic into any of the methods that deal with passwords and password answers (i.e. ValidateUser, ChangePassword, and so on). However, if a provider doesn’t support auto-unlocking behavior, it should support explicitly unlocking a user account via the UnlockUser method. At a minimum an unlocked user account should have its IsLockedOut property set to false. Typically, internal tracking counters are reset as well, and the LastLockoutDate property for the user can be reset to a default value. If a provider doesn’t cause users to be locked out, or if some other mechanism outside of Membership is used to unlock users, a custom provider should throw a

NotSupportedException instead.

389

Chapter 10

Supporting Self-Service Password Reset or Retrieval

Several properties provide information about the self-service password reset and password retrieval capabilities of Membership. The general idea behind this feature is that website users can retrieve their password, or have the system reset their password, if they forget the original password. Typically, for enhanced security the user needs to answer a special password question before the system retrieves or resets the password.

Although you may author a provider that supports only one of these options (that is, only password retrieval or only password resets), or none of these options, you should still implement the following properties so that server controls and administrative tools can determine the level of support that a custom provider has for password reset and retrieval:

EnablePasswordRetrieval — Indicates whether the provider instance allows passwords to be retrieved in an unencrypted format. If you author a provider that supports password storage with reversible encryption, the value of this property may be retrieved from a provider configuration attribute just as it is with the SqlMembershipProvider. If you never plan to support this functionality just return false. The PasswordRecovery control in Login controls looks at the value of this property to determine what kind of UI to render.

EnablePasswordReset — Indicates whether the provider allows a user’s password to be reset to a randomly generated password value. As with the SqlMembershipProvider, you can derive this value from your provider’s configuration. If you don’t plan on ever supporting this functionality, you can instead always return false from this property. The PasswordRecovery control also looks at this property value to determine what kind of UI to render.

RequiresQuestionAndAnswer — If your provider requires that a password question be successfully answered before performing either a password reset or retrieving a password, then you would return true from this property. As with the previous two properties this value can be driven from configuration as the SqlMembershipProvider does. Or if you don’t support this kind of functionality just return false. The CreateUser control in the Login controls uses this property to determine whether it should prompt a new user for a password question and answer. The PasswordRecovery control in the Login controls also looks at this property value to determine whether or not it should challenge the user before resetting or retrieving a password.

PasswordFormat — Indicates the way in which passwords will be stored in a backend system by the provider. Providers that are configurable such as the SqlMembershipProvider can derive this value from configuration. Other providers such as the ActiveDirectoryMembershipProvider always return a hard-coded value because the underlying data store only supports a single storage format. None of the Login controls directly depend on this property. However, you may write Membership-related logic that only makes sense for certain password formats. For example, sending an email with the person’s old password is never going to work unless the provider stores the password using reversible encryption as opposed to hashing.

The methods related to password resets and password retrieval are described in the following list. In some cases password reset and retrieval influences only part of the parameter signature of a method. In other cases, entire methods can be stubbed out if you don’t plan on supporting either piece of functionality.

CreateUser — You can always create a user even if you don’t plan on implementing password resets and password retrieval. The passwordQuestion and passwordAnswer parameters to this method will be important to you if your provider returns true from RequiresQuestion AndAnswer. Developers will probably expect your CreateUser implementation to enforce the

390

Membership

requirement that both parameters be supplied in the event you return true from RequiresQuestionAndAnswer. Note that if you want to, you can choose not to support password resets or retrieval and yet still require a question and answer. Though not recommended, this would give your provider two extra properties for storing user-related data. From a security perspective, a custom provider should always store the password answer in a secure format. Because the password answer is essentially a surrogate password, providers should not store the password answer in cleartext.

ChangePasswordQuestionAndAnswer — This method should be implemented if your provider returns true from RequiresQuestionAndAnswer. If you don’t implement this method, then after a new user account is created your users won’t have the ability to ever change their secret password question and answer. Because this method requires a user’s password in order to complete successfully, providers that keep track of bad password attempts should increment their tracking counters in this method and lock out users as necessary. Providers also need to handle the case where a user is already locked out; locked out users should not be allowed to change their password question and answer. If your provider doesn’t use password questions and answers (either you don’t support reset/retrieval or you don’t want to impose the added security measure of a question-answer challenge), then you should throw a

NotSupportedException from this method.

GetPassword — Implement this method if your provider is able to store passwords with reversible encryption and you want to give your users the ability to retrieve their old passwords. On one hand, if a custom provider requires a password answer prior to retrieving a password, and if the provider also keeps track of bad password answer attempts, it should increment tracking counters from inside of this method and lock out users as necessary. Providers need to also handle the case where a user is already locked out — in which case, locked out users should not be allowed to retrieve their password even if they have a valid answer. On the other hand, if a custom provider does not require an answer, then it can just ignore the answer parameter. If your provider’s underlying data store doesn’t support reversible encryption or if you don’t want this type of functionality to be available, then throw a NotSupportedException instead. The PasswordRecovery control in the Login controls will use this method if it detects that the current provider supports password retrieval (that is, EnablePasswordRetrieval returns true). Note that if your provider doesn’t require a valid password answer to a password question (that is, RequiresQuestionAndAnswer returns false), then your provider should ignore the answer parameter to this method.

ResetPassword — If your provider allows users to reset their own passwords, then your provider should implement this method. If a provider supports extensible password validation routines, it should raise the ValidatingPassword event from this method as well. The PasswordRecovery control in the Login controls will use this method if your provider returns true from EnablePasswordReset. If a custom provider requires a password answer prior to resetting a password, and if the provider also keeps track of bad password answer attempts, it should increment tracking counters from inside of this method and lock out users as necessary. Providers also need to handle the case where a user is already locked out — in which case, locked out users should not be allowed to reset their password even if they have a valid answer. However, if a custom provider doesn’t require an answer, it can just ignore the answer parameter. If a custom provider doesn’t support password resets, your provider should return a NotSupportedException from this method. When resetting passwords, a custom provider can call the Membership.GeneratePassword static helper method. This method can be used to auto-generate a valid random password that meets minimum length and minimum nonalphanumeric character requirements. Note though that this helper method cannot guarantee

a random password that matches a password strength regular expression; attempting to

391