I’m working on a new project which has a few unique security requirements for membership providers:
- Passwords can’t be used again within 4 times of first use
- 1-time pins must be used for authentication
- User accounts must have validity periods that administrators can set (this account is valid between 1-jun-2010 and 14-jun-2010)
- Accounts must be auto-disabled after 90 days of inactivity
- Passwords have to be changed every 90 days
Rather than hardcode the values (4, 90, 90) I decided it would be best to make them parameters which the membership provider could simply read from the config.
While this is such a simple thing to do it did throw me a curve ball before I realized what was going on.
The setup:
///
/// Initializes the config values
///
/// The name that the provider has been given
/// The NameValueCollection from the config settings
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
string passwordHistoryNumberConfig = config["PasswordHistoryNumber"];
if (!string.IsNullOrEmpty(passwordHistoryNumberConfig) && int.TryParse(passwordHistoryNumberConfig, out _passwordHistoryNumber))
{
// No need to do anything here
}
else
{
// Either the config was empty or it wasn't a valid int. Either way, default to 4
_passwordHistoryNumber = 4;
}
base.Initialize(name, config);
}
Simple enough, simply read the value from the NameValueCollection and done right? Unfortunately no.
The Problem
If you run this and step into the method with a debugger everything seems to be working fine but as soon as your page renders you’ll get a horrible “Server error in application” window.
A quick look inside the web.config shows that there is no custom configuration area defined specifically for membership providers so where does the membership provider get it’s list of valid attributes? Firing up Reflector on SqlMembershipProvider.Initialize gives us a method (an untidy one) that looks similar to:
public override void Initialize(string name, NameValueCollection config)
{
HttpRuntime.CheckAspNetHostingPermission(AspNetHostingPermissionLevel.Low, "Feature_not_supported_at_this_level");
if (config == null)
{
throw new ArgumentNullException("config");
}
if (string.IsNullOrEmpty(name))
{
name = "SqlMembershipProvider";
}
if (string.IsNullOrEmpty(config["description"]))
{
config.Remove("description");
config.Add("description", SR.GetString("MembershipSqlProvider_description"));
}
base.Initialize(name, config);
this._SchemaVersionCheck = 0;
this._EnablePasswordRetrieval = SecUtility.GetBooleanValue(config, "enablePasswordRetrieval", false);
this._EnablePasswordReset = SecUtility.GetBooleanV.....
And a little lower down the method we also see:
config.Remove("connectionStringName");
config.Remove("connectionString");
config.Remove("enablePasswordRetrieval");
config.Remove("enablePasswordReset");
config.Remove("requiresQuestionAndAnswer");
config.Remove("applicationName");
config.Remove("requiresUniqueEmail");
config.Remove("maxInvalidPasswordAttempts");
config.Remove("passwordAttemptWindow");
config.Remove("commandTimeout");
config.Remove("passwordFormat");
config.Remove("name");
config.Remove("minRequiredPasswordLength");
config.Remove("minRequiredNonalphanumericCharacters");
config.Remove("passwordStrengthRegularExpression");
config.Remove("passwordCompatMode");
if (config.Count > 0)
{
string key = config.GetKey(0);
if (!string.IsNullOrEmpty(key))
{
throw new ProviderException(SR.GetString("Provider_unrecognized_attribute", new object[] { key }));
}
}
So that's it! The SqlMembershipProvider expects that all config values will be removed from the in-memory config object and if there's any left, it wasn't a valid config element. I can see the logic there... wanting to enforce that only valid properties are placed on the config element.
That also means it’s an easy fix to change our custom provider:
/// Initializes the config values
///
/// The name that the provider has been given
/// The NameValueCollection from the config settings
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
string passwordHistoryNumberConfig = config["PasswordHistoryNumber"];
if (!string.IsNullOrEmpty(passwordHistoryNumberConfig) && int.TryParse(passwordHistoryNumberConfig, out _passwordHistoryNumber))
{
// Remove the PasswordHistoryNumber element from the config file
config.Remove("PasswordHistoryNumber");
}
else
{
// Either the config was empty or it wasn't a valid int. Either way, default to 4
_passwordHistoryNumber = 4;
}
base.Initialize(name, config);
}
And that’s it!

No comments have been posted yet