Friday, November 28, 2008

How to configure a membership provider programmatically

The following C# sample code demonstrates how to configure a .NET membership provider programmatically. This code requires that you also configure a connection string named MyDatabase.

NameValueCollection objConfig = new NameValueCollection();
objConfig.Add("connectionStringName", "MyDatabase");
objConfig.Add("enablePasswordRetrieval", "false");
objConfig.Add("enablePasswordReset", "true");
objConfig.Add("requiresQuestionAndAnswer", "true");
objConfig.Add("applicationName", "MyApp");
objConfig.Add("requiresUniqueEmail", "true");
objConfig.Add("maxInvalidPasswordAttempts", "5");
objConfig.Add("passwordAttemptWindow", "10");
objConfig.Add("commandTimeout", "30");
objConfig.Add("passwordFormat", "Hashed");
objConfig.Add("name", "AspNetSqlMembershipProvider");
objConfig.Add("minRequiredPasswordLength", "8");
objConfig.Add("minRequiredNonalphanumericCharacters", "2");
objConfig.Add("passwordStrengthRegularExpression", "(?=^.{8,25}$)(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*()_+}{\\":;'?/>.<,])(?!.*\\s).*$"));

SqlMembershipProvider objSqlMembershipProvider = new SqlMembershipProvider();
objSqlMembershipProvider.Initialize(objConfig["name"], objConfig);
MembershipProviderCollection colMembershipProviders = new MembershipProviderCollection();
colMembershipProviders.Add(objSqlMembershipProvider);
colMembershipProviders.SetReadOnly();

BindingFlags enuBindingFlags = BindingFlags.NonPublic | BindingFlags.Static;
Type objMembershipType = typeof(Membership);
objMembershipType.GetField("s_Initialized", enuBindingFlags).SetValue(null, true);
objMembershipType.GetField("s_InitializeException", enuBindingFlags).SetValue(null, null);
objMembershipType.GetField("s_HashAlgorithmType", enuBindingFlags).SetValue(null, "SHA1");
objMembershipType.GetField("s_HashAlgorithmFromConfig", enuBindingFlags).SetValue(null, false);
objMembershipType.GetField("s_UserIsOnlineTimeWindow", enuBindingFlags).SetValue(null, 15);
objMembershipType.GetField("s_Provider", enuBindingFlags).SetValue(null, objSqlMembershipProvider);
objMembershipType.GetField("s_Providers", enuBindingFlags).SetValue(null, colMembershipProviders);

This code assumes you have the following statements in your cs file:

using System.Web.Security;
using System.Collections.Specialized;
using System.Reflection;

Then you can use Membership functions like Membership.ValidateUser, Membership.GetUser and Membership.CreateUser.

10 comments:

beernutz said...
This comment has been removed by the author.
beernutz said...

Just a note while i am trying to get this this code working:

objConfig.Add("passwordFormat", "Hash"));

should be

objConfig.Add("passwordFormat", "Hashed"));

and

BindingFlags enuBindingFlags = BindingFlags.NonPublic BindingFlags.Static;

should be

BindingFlags enuBindingFlags = BindingFlags.NonPublic | BindingFlags.Static;

beernutz said...

One final note:

objConfig.Add("connectionStringName", "DB");

for some reason connectionStringName is set to "DB" instead of what you set it to earlier. Not sure why, but this is what i see on my debugger.

Thank you for this code. It really helped!

Jay said...

Thanks so much for posting this article. Worked like a charm.

I just had a couple compile errors to fix: relating to objConfig.Add()); having 2 ending ).

The regex should have \\d and \\s

vchanis said...

This has been a very helpful post! I would like share my scenario, for people who might be searching the web for something similar.

I have a web application where the WEB server does not have access to the DB server, thus all DB interaction is done through an APP server, via remoting.

My specific need, which was helped by this post, was to be able to create a remote object which encapsulates an instance of type SqlMembershipProvider. The Membership object in the web server is created in Global.asax Application_Start and stored in the Application collection of objects, so as to be shared accross the application (as is the case when you use the default MembershipProvider object of Microsoft).
There are two choices as to how the remote object is configured: Client-Activated Object (CAO) or Singleton Server-Activated Object (SAO).
When CAO is used, everything worked fine, except for the (unacceptable) limitation that in case the Remote Host Application was restarted the CAO is rendered useless, which means I have no membership in my web app, and thus the web app needs to be restarted.
So, Singleton SAO (with an infinite leaseTime) is the way to go, if restartability (and scalability) is needed. But when I configured it as Singleton SAO I had an error come up, when I tried to call .GetUser in any webpage. The error is : The attribute 'connectionStringName' is missing or empty.

vchanis said...

On the Remote Host Application (running on the APP server)
my code for the remote object:

public class MyMembership : MarshalByRefObject, IDisposable
{

private SqlMembershipProvider _sprov;


public MembershipUser GetUser(object providerUserKey)
{
return _sprov.GetUser(providerUserKey, true);
}

public MembershipUser GetUser(object providerUserKey, bool userIsOnline)
{
return _sprov.GetUser(providerUserKey, userIsOnline);
}

public MembershipUser GetUser(string username)
{
return _sprov.GetUser(username, true);
}

public MembershipUser GetUser(string username, bool userIsOnline)
{
return _sprov.GetUser(username, userIsOnline);
}

public bool ValidateUser(string username, string password)
{
return _sprov.ValidateUser(username, password);
}

public MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
{
return _sprov.CreateUser(username, password, email, passwordQuestion, passwordAnswer, isApproved,
providerUserKey, out status);
}

public MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, out MembershipCreateStatus status)
{
return _sprov.CreateUser(username, password, email, passwordQuestion, passwordAnswer, isApproved,
null, out status);
}

public bool DeleteUser(string username, bool deleteAllRelatedData)
{
return _sprov.DeleteUser(username, deleteAllRelatedData);
}

//the following constructor is called when SAO activation model is used and the config params are gathered on the host side of remoting (i.e. the app server)
public MyMembership()
{
NameValueCollection mem = Utils.GetMembershipParams();
_sprov = new SqlMembershipProvider();
_sprov.Initialize("MySqlMembershipProvider", mem);

}

//the following constructor is called when CAO activation model is used adn the config params are passed from the client side of remoting (i.e. the web server)
public MyMembership(NameValueCollection config)
{
_sprov = new SqlMembershipProvider();
_sprov.Initialize("MySqlMembershipProvider", config);
}

public void Dispose()
{
_sprov = null;
}
}

vchanis said...

This code was giving me the above mentioned error, when calling .GetUser in any webpage. But when I changed the default constructor to the following, it worked like a charm!

public MyMembership()
{
NameValueCollection mem = Utils.GetMembershipParams();
_sprov = new SqlMembershipProvider();
_sprov.Initialize("DPaySqlMembershipProvider", mem);


MembershipProviderCollection colMembershipProviders = new MembershipProviderCollection();
colMembershipProviders.Add(_sprov);
colMembershipProviders.SetReadOnly();

BindingFlags enuBindingFlags = BindingFlags.NonPublic | BindingFlags.Static;
Type objMembershipType = typeof(Membership);
objMembershipType.GetField("s_Initialized", enuBindingFlags).SetValue(null, true);
objMembershipType.GetField("s_InitializeException", enuBindingFlags).SetValue(null, null);
objMembershipType.GetField("s_HashAlgorithmType", enuBindingFlags).SetValue(null, "SHA1");
objMembershipType.GetField("s_HashAlgorithmFromConfig", enuBindingFlags).SetValue(null, false);
objMembershipType.GetField("s_UserIsOnlineTimeWindow", enuBindingFlags).SetValue(null, 15);
objMembershipType.GetField("s_Provider", enuBindingFlags).SetValue(null, _sprov);
objMembershipType.GetField("s_Providers", enuBindingFlags).SetValue(null, colMembershipProviders);
}


A note to whoever might want to use this code: the line NameValueCollection mem = Utils.GetMembershipParams();
basically does what the following lines of the original post do:

NameValueCollection objConfig = new NameValueCollection();
objConfig.Add("connectionStringName", "MyDatabase");
objConfig.Add("enablePasswordRetrieval", "false");
objConfig.Add("enablePasswordReset", "true");
objConfig.Add("requiresQuestionAndAnswer", "true");
objConfig.Add("applicationName", "MyApp");
objConfig.Add("requiresUniqueEmail", "true");
objConfig.Add("maxInvalidPasswordAttempts", "5");
objConfig.Add("passwordAttemptWindow", "10");
objConfig.Add("commandTimeout", "30");
objConfig.Add("passwordFormat", "Hashed");
objConfig.Add("name", "AspNetSqlMembershipProvider");
objConfig.Add("minRequiredPasswordLength", "8");
objConfig.Add("minRequiredNonalphanumericCharacters", "2");
objConfig.Add("passwordStrengthRegularExpression", "(?=^.{8,25}$)(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*()_+}{\\":;'?/>.<,])(?!.*\\s).*$"));


So, again thanks for the helpful post Jacques!

Triynko said...

This is cool. I can actually run this as a standalone app outside of any web application. I just change "connectionStringName" to "connectionString" and specify an actual connection string, so it doesn't go looking for any settings in web.config. I had to change target platform to .NET Framework 4 (not Client Profile!), so I could include the System.Web.dll and System.Web.ApplicationServices.dll. I then specify the correct application name, which for the root of the web site is "/", and then I can grab any user and reset their password! Thanks! Once you reset a users password, you can then log in as them, and access password reset screens, etc.

Dipti Tilani said...

How can we configure Role manager provider same way?? is it possible

Chris Carter said...

Still works like a champ, thanks!