Wednesday, July 23, 2014

Resetting password without knowing old password and also by providing secret answer together in one application

In some application we might want to let user change their passwords through secret answer or by entering their old password or without getting their old password.
What if user forgets these information and we want an admin to generate new temporary password for user so they can login and change their password again.
These all things cannot be achieved by just providing one membership provider. Therefore, we would need to add two membership provider and will switch to appropriate provider when required.

Lets consider below two config settings for membership provider


 <membership defaultProvider="SqlServerMembershipProvider" userIsOnlineTimeWindow="10" hashAlgorithmType="HMACSHA512">  
    <providers>  
     <clear />  
     <add name="SqlServerMembershipProvider" requiresQuestionAndAnswer="false" passwordFormat="Hashed" />  
     <add name="SqlServerMembershipProviderRequiresSecretAnswer" requiresQuestionAndAnswer="true" passwordFormat="Hashed" />  
    </providers>  
   </membership>  

I have removed the unrelated attributes from the providers for the brevity. The change in above two providers is requiredQuestionAndAnswer="true" in one provider and in other it is set to false.

Normally for a user to change password the process is they click on forgot password link
Then we ask for username or email and then we sent email to user with reset password link and on that specific link we ask user to provide new password. This process is fine as we are updating password without knowing new password.
Most of the developer would write below code to update password
 
  public bool UpdateMemberPassword(string username, string newPassword)  
     {  
       if (string.IsNullOrWhiteSpace(username))  
         throw new ArgumentNullException("username");  
       if (string.IsNullOrWhiteSpace(newPassword))  
         throw new ArgumentNullException("newPassword");  
       MembershipUser user = GetMemberByUsername(username);  
       if (user == null)  
         throw new Exception("user could not be found");  
       // Membership change password without knowing the old password http://stackoverflow.com/questions/5013901/asp-net-membership-change-password-without-knowing-old-one  
       bool isChanged = user.ChangePassword(user.ResetPassword(), newPassword);  
       return isChanged;  
     }  

Now if you also want to allow the user to change password through secret question and answer then the default provider would not work as it has set requireQuestionAnswer="false".




membershipProvider.ResetPassword(userName, answer); 

 
 
 The provided function will always update the user password regardless the provided answer is correct or not as the required secret queston answer is set to false.

So how do we work around. Here the second provider comes into play which has set requrieQuestionAnswer="true"

If user wants to change password through secret question and answer you will have to change the function as below

 
 public bool ValidateAnswerForUser(string userName, string answer)  
     {  
       if (string.IsNullOrWhiteSpace(userName))  
         throw new ArgumentNullException("userName");  
       if (string.IsNullOrWhiteSpace(answer))  
         throw new ArgumentNullException("answer");  
       MembershipUser user = GetMemberByUsername(userName);  
       if (user == null)  
         throw new ArgumentNullException("user does not exists");  
       string password;  
       try  
       {  
         /* The reason for using different membership provider other than default one is that,  
         * the default has set requiresQuestionAndAnswer="false" so, even if you provide the wrong secret answer provider will simply reset the password.  
         * Therefore, here i am switching the provider with configuration requiresQuestionAndAnswer="true" so if user provides the wrong answer then it will throw exception"  
         */  
         var membershipProvider = Membership.Providers["SqlServerMembershipProviderRequiresSecretAnswer"];  
         if (membershipProvider != null)  
         {  
           password = membershipProvider.ResetPassword(userName, answer);  
         }  
         else  
         {  
           password = Membership.Provider.ResetPassword(userName, answer);  
         }  
       }  
       catch (MembershipPasswordException e)  
       {  
         return false;  
       }  
       if (string.IsNullOrWhiteSpace(password))  
         return false;  
       return true;  
     }  

This will update the password only if provided answer matches the answer in system.