Friday, February 15, 2008

Customizing the ChangePassword control and removing the required CurrentPassword field

It's very rare that what is already provided in asp.net under the Login controls fits my requirements out of the box without some tweaking. Not that any of these controls offer anything specialized, but certainly they are a big time saver if we can re-utilize their functionality.

First some background as to why i personally want to customize the ChangePassword control to suit my needs :

Password recovery is what i was after today, however i have hashed passwords, and recovery is impossible. If the user lost their password, then there is no way for me to know what their password is and send it back in clear text.

The ideal solution is to reset the password, however the autogenerated password is quite ugly and quite hard to remember. What I've decided to do is send the email during password recovery, but as part of the email, instead of telling the user their old password(which i can't).

I'm instead going to ask them to click on a tokenized link that will guarantee to me that they are indeed the ones that requested the password, send them to the page where they can provide a new password, in the background i'd be autogenerating a password first ofcourse, then updating the password with their new password because the MembershipUser.ChangePassword(oldPassword, newPassword) method requires Old password as one of it's two parameters.

This password change step, i'd like to be done using the ChangePassword control, however to my big surprise CurrentPassword Field is a required field that i cannot remove. This is also a field that I do not want asked for during the password change request(since my user has forgotten their password and are now going to provide their new pasword).

There is ofcourse no property or method in this control that removes the CurrentPassword field requirement, below is a screenshot of the ChangePassword control in designview, as you can note, the highlighted field is the CurrentPassword field i do not want.


I've done a quick look on google and in the asp.net/forums and didn't find anybody providing any proper solutions either, mostly vague replies : http://forums.asp.net/p/1189347/2038354.aspx

As you can read from the posts there, the issue seems to be two things which were also my same issues :
1) Remove the current password label/TextBox
2) Pass the new resetpassword to CurrentPassword Property which by the way is a getter only and not settable (SAD SAD)

Both of these things are not supported in this control. So let's quickly fix requirement 1 and there are a couple of ways to fix this :
a) You have to define a custom  <ChangePasswordTemplate>. This can be easily done by taking your ChangePassword control into DesignView in Visual studio, right click on the control and select "Convert to template". You can then switch to HtmlView and set the visibility of CurrentPasswordLabel, CurrentPassword and CurrentPasswordRequired controls.

b) If you prefer to do this in code, then you can find the Label and TextBox for CurrentPassword and set its visiblity to false. Since a is a nobrainer, i'm including a sample code of method (b) :
Label l = (Label)changePassword1.ChangePasswordTemplateContainer.
         FindControl("CurrentPasswordLabel");
if (l != null)
{
    l.Visible = false;
}
TextBox tb = (TextBox)changePassword1.ChangePasswordTemplateContainer.
FindControl("CurrentPassword");
if (tb != null)
{
    tb.Visible = false;
}
RequiredFieldValidator rfv = 
        (RequiredFieldValidator)changePassword1.
ChangePasswordTemplateContainer.FindControl("CurrentPasswordRequired");
if (rfv != null)
{
    rfv.Visible = false;
}

Now that we have the fields we want disabled, let's head onto fix issue 2 :
We can't pass the Autogenerated password to the CurrentPassword Property because its a getter only, however this getter returns the value from our CurrentPassword TextBox, and this job is done immidiately after ChangingPassword event fires. This is good news for us, so we can resolve issue 2 like this :
void changePassword1_ChangingPassword(object sender, 
LoginCancelEventArgs e)
{
    changePassword1.UserName = user.UserName;
    TextBox currentPassword = (TextBox)changePassword1.
    ChangePasswordTemplateContainer.FindControl("CurrentPassword");
    if (currentPassword != null)
    {
        currentPassword.Text = user.ResetPassword();
    }
}

Note that in the above code, user is a reference to a field of type MembershipUser. Ok, that's it. Now we have what were after, look at the screenshot below :

10 comments:

  1. Nice, I think most people instantly change the randomly generated password to something more useful, so just giving them a link to do this reduces the number of steps necessary :) You could also put a link in the e-mail that cancels the token in case the user didn't request it himself!

    ReplyDelete
  2. Filip, yes, the email for token invalidation is a very good idea indeed. Many thanks :D

    ReplyDelete
  3. Is there any way to prevent the password reset from logging in the user whose password we are resetting?



    I really want to use this example in ad administrative section but logging out the admin is not desirable.



    Otherwise this code works perfect as advertised.



    Thanks

    ReplyDelete
  4. JAndrews, Yes, you can do that. Basically the control is logging in the current user based on a certain condition.



    It only logs the user in if the MembershipUser is approved, note that MembershipUser has an approved property or if the MembershipUser is not Locked out. So if both these conditions are true in your case, you can temporarily set approve to false on the membership user and update the membership, during the ChangingPassword event and re-enable it again immidiately in the ChantedPassword Event.



    This is a workaround ofcourse, and since this is an admin feature, the extra hits to your db for these two operations might not seem like a big deal, so all in all i find this workaround feasible.



    Good luck,

    Alessandro

    ReplyDelete
  5. Do you use the PasswordRecovery control to manage the reset? If so, how do you retrieve the new (temporary) password so that you can tokenize it? I've look all over and cannot find it.



    Thanks

    ReplyDelete
  6. Thanks for your kind reply.This code will very helpful to me to overcome the problem.So i really thankful to you people.

    ReplyDelete
  7. bakwaassssssssssssssssssssssssssss

    ReplyDelete
  8. An other solution for the RequiredFieldValidator is to put Junk in that field and change it in the ChangingPassword !!



    How can I make an other user (depending on role) to be able to change any users passwords, because when the change is confirmed, that user becomes the logged one.

    ReplyDelete
  9. I'm getting closer just need to learn how to make a tokenized link

    ReplyDelete
  10. Good one.. i think there are very few resources on this.

    ReplyDelete