Wednesday, April 22, 2009

Extend SqlMembershipProvider and access hidden information.

This article describes how to override some of the basic functionality in the SqlMembershipProvider class. Of course every article like this started out as a problem. This article is a discussion of the problem and what I did to solve it.

The Problem:
I inherited a website which used the MembershipProvider functionality of the Microsoft Enterprise Library. One of the pages was a "Forgot Password" screen. Now this particular site used a hashed password format so retrieval of the newly generated password was not possible. This was desired. However, the client did want to store the newly generated passwords as a means of telling whether or not a user had logged in and changed the password. Unfortunately, the site had implemented this functionality in the form of a PasswordRecovery object. All that functionality, including mailing the user their password, was behind the scenes. So the question is, how do I get that generated password on the server side.

Solution:
The solution I came up with was actually quite easier than I thought it would be. The short answer is to create my own membership provider class and override the GeneratePassword functionality. Now I didn't want to change that behavior, only capture the newly generated password for that user. This password is to eventually be stored in the comment field of the aspnet_Membership table. Now, on to code.

First thing I did was to create my own provider class by inheriting from the SqlMembershipProvider class. The code looks something like this.
public class MyMembershipProvider :

System.Web.Security.SqlMembershipProvider

{

public string GeneratedPassword;

public MyMembershipProvider()

: base()

{

}

public override string GeneratePassword()

{

GeneratedPassword = base.GeneratePassword();

return GeneratedPassword;

}

}
Note that I am inheriting from the SqlMembershipProvider class and overriding the GeneratePassword function. Within that function, I still call the base.GeneratePassword to do the actual generation but I save it off in an attribute. We will come back to this later. The next step is how do I use my new class?

In the markup of the page, I located the PasswordRecovery control and the MembershipProvider attribute. That control looks something like this:
<asp:PasswordRecovery ID="PasswordRecovery1" runat="server" MembershipProvider="AspNetSqlMembershipProvider" OnSendingMail="PasswordRecovery1_SendingMail"

OnVerifyingUser="PasswordRecovery1_VerifyingUser">
This value is then located in the Web.Config class:
        <membership>

<providers>

<remove name="AspNetSqlMembershipProvider" />

<add name="AspNetSqlMembershipProvider" type="MyMembershipProvider" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" passwordFormat="Hashed" applicationName="/" maxInvalidPasswordAttempts="10000" />

</providers>

</membership>

Note the Type attribute is the name of my class. At this point, your class is fully hooked in and is being used transparently by the password recovery control. Within the page code, I would like to now access the generated password before it is emailed. Withing the On_SendingEmail function, I have the following code.

protected void PasswordRecovery1_SendingMail(object sender, MailMessageEventArgs e)

{

PasswordRecovery passwordRecovery = (PasswordRecovery)sender;

string user = passwordRecovery.UserName;



_loginUser = Membership.Provider.GetUser(user, false);

_loginUser.Comment = ((MyMembershipProvider)Membership.Provider).GeneratedPassword;

Membership.UpdateUser(_loginUser);

}

Within this code I use the password recovery object to get the user name which is then used to lookup the user. In order to access the class attribute GeneratedPassword, I have to cast the Membership.Provider to ((MyMembershipProvider)Membership.Provider). Now, I can get the generated password, put it in the comment field, and update the user record.