<

Extending a Membership Provider

Published on
11,978 Points
5,678 Views
3 Endorsements
Last Modified:
Lots of people ask this question on how to extend the “MembershipProvider” to make use of custom authentication like using existing database or make use of some other way of authentication. Many blogs show you how to extend the membership provider class but don’t give you what other class they have used in the backend.  With this EE Article, I would not only show how to extend MembershipProvider class but will give you the full source code of the web service which is used instead of a database as a backend.

I have made use of the MembershipProvider many a times but I would like to share my experience in one of my project where I had to extend the MembershipProvider class to make use of an already existing web service of the client to validate user, create user, change password etc.   I will show how to extend MembershipProvider class and write your custom MembershipProvider class.

Since I cannot reveal what webserivce was used in the website developed for the client, I will create my own webservice to mimic the exact behavior as in the website. So let's start by creating a webservice which will have methods to create a user, validate user credentials and finally change the user’s password. Once the webservice is done, we will extend the membership class.  Below is the code for the webservice.

WebService program code
[WebService(Namespace = "http://tempuri.org/")] 
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] 
public class WebServiceAuthentication : System.Web.Services.WebService 
{ 
    string xmlFilePath = string.Empty; 
    public WebServiceAuthentication() 
    { 
        xmlFilePath = Server.MapPath("data/users.xml");        
    }

    [WebMethod] 
    public bool ChangePassword(string username, string newpassword) 
    { 
        XDocument users = XDocument.Load(xmlFilePath); 
        var user = from u in users.Elements("user") 
                   where u.Element("username").Value.ToLower() == username.ToLower() 
                   select u; 
        if (user != null && user.Count() > 0) 
        { 
            user.First().Element("password").Value = newpassword; 
            return true; 
        }

        return false; 
    }

    [WebMethod] 
    public bool ValidateUser(string userName, string passWord) 
    { 
        XDocument users = XDocument.Load(xmlFilePath); 
        var user = from u in users.Element("users").Elements("user") 
                   where u.Element("username").Value.ToLower() == userName.ToLower() && u.Element("password").Value == passWord 
                   select u; 
        if (user != null && user.Count() > 0) 
            return true; 
        return false; 
    }

    [WebMethod] 
    public Customer CreateUser(string username, string password, string emailID) 
    { 
        XDocument users = XDocument.Load(xmlFilePath); 
        var user = from u in users.Elements("user") 
                   where u.Attribute("email").Value.ToLower() == username.ToLower() 
                   select u.Attribute("email").Value; 
        if (user.Count() <= 0) 
            throw new Exception("Email already in user"); 
        else 
        { 
            users.Element("users").Add(new XElement("user", 
                new XAttribute("email", emailID), 
                new XElement("userName", username), 
                new XElement("password", password))); 
            users.Save(xmlFilePath); 
        }

        return new Customer { UserName = username }; 
    }

    [WebMethod] 
    public string ResetPassword(string username) 
    { 
        string newPass = string.Empty; 
        XDocument users = XDocument.Load(xmlFilePath); 
        var user = from u in users.Elements("user") 
                   where u.Element("username").Value.ToLower() == username.ToLower() 
                   select u; 
        if (user != null && user.Count() > 0) 
        { 
            newPass = Guid.NewGuid().ToString().Substring(0, 10); 
            user.First().Element("password").Value = newPass; 
        } 
        else 
            throw new Exception("User not found.");

        return newPass; 
    }

    [WebMethod] 
    public bool UpdateCustomer(Customer customerToBeUdpated) 
    {        
        XDocument users = XDocument.Load(xmlFilePath); 
        var userToBeUpdated = (from u in users.Elements("user") 
                              where u.Attribute("email").Value.ToLower() == customerToBeUdpated.UserName.ToLower() 
                              select u); 
        if (userToBeUpdated != null && userToBeUpdated.Count() > 0) 
        { 
            XElement user = userToBeUpdated.First(); 
            user.Element("password").Value = customerToBeUdpated.Password; 
            if (user.Element("firstname") != null) 
                user.Element("firstname").Value = customerToBeUdpated.FirstName; 
            else 
                user.Add(new XElement("firstname", customerToBeUdpated.FirstName)); 
            if (user.Element("lastname") != null) 
                user.Element("lastname").Value = customerToBeUdpated.LastName; 
            else 
                user.Add(new XElement("lastname", customerToBeUdpated.LastName)); 
            if (user.Element("address") != null) 
            { 
                user.Element("address").Element("street").Value = customerToBeUdpated.Address.Street; 
                user.Element("address").Element("city").Value = customerToBeUdpated.Address.City; 
                user.Element("address").Element("state").Value = customerToBeUdpated.Address.State; 
                user.Element("address").Element("country").Value = customerToBeUdpated.Address.Country; 
            } 
            else 
            { 
                user.Add(new XElement("address", new XElement("street", customerToBeUdpated.Address.Street), 
                    new XElement("city", customerToBeUdpated.Address.City), 
                    new XElement("state", customerToBeUdpated.Address.State), 
                    new XElement("country", customerToBeUdpated.Address.Country))); 
            }

            return true; 
        }

        return false; 
    } 
}

Open in new window


In the above web service we have methods to change the password (ChangePassword), to validate user credential based on user name and password (ValidateUser), method to create an user (CreateUser), method to change the password (ResetPassword) and finally method to update user details (UpdateCustomer).  

All these methods work on a XML file and make use of LINQ to XML to create user, change password etc.  As mentioned before, all the data is stored and retrieved from a XML file called Users.xml.  If a new user has to be added or user details needs to be updated or password needs to be changed, everything is done in the XML.  The web service makes use of a XML as a storage medium.  The XML structure is pasted below.

Data storage file (Users.xml)
<?xml version="1.0" encoding="utf-8" ?> 
<users> 
  <user email="a@a.com"> 
    <username>sandeep</username> 
    <password>pass</password> 
    <firstname>Sandeep</firstname> 
    <lastname>P.R</lastname> 
    <address> 
      <street>Blah blah</street> 
      <city>City</city> 
      <state>State</state> 
      <country>Country</country> 
    </address> 
  </user> 
  <user email="sndppr@gmail.com"> 
    <username>sndppr@gmail.com</username> 
    <password>pass</password> 
    <firstname>Sandeep</firstname> 
    <lastname>P.R</lastname> 
    <address> 
      <street>Blah blah</street> 
      <city>City</city> 
      <state>State</state> 
      <country>Country</country> 
    </address> 
  </user> 
</users>

Open in new window


The XML is pretty much straightforward.  The XML stores the user name, password and other user related details like his address and email ID. In the XML, the password is saved as plain text which is not a good way of storing password -- this is just an example!  When storing passwords in a physical file, you must use some sort of encryption.

Some of the methods of the web service return “Customer” as an object.  The “Customer” class is also a very straightforward class. It derives from the “System.Web.Security.MembershipUser” class and adds some of its own properties. The “Customer” class code is pasted below. The "Customer" class is derived from "MembershipUser" class so that a "Customer" object can be returned from the various "MembershipProvider" class methods.

Customer class
public class Customer : System.Web.Security.MembershipUser 
{ 
    public Customer() 
    {

    }

    private string userName = string.Empty;

    public string FirstName { get; set; } 
    public string LastName { get; set; } 
    public string UserName { get; set; } 
    public string EMail { get; set; } 
    public string Password { get; set; } 
    public Address Address { get; set; } 
}

public class Address 
{ 
    public string Street { get; set; } 
    public string City { get; set; } 
    public string State { get; set; } 
    public string Country { get; set; } 
}

Open in new window


That’s about it for the web serivce and the XML file which form the backend.

Extending the MembershipProvider

Now let's try to extend System.Web.Security.MembershipProvider to work in conjunction with the above web service.  Before that let's keep some things in mind.  If you are not planning to make use of question and answer to reset the password then make sure that you return false from the “RequiresQuestionAndAnswer” property.  If you leave the default implementation then the system will throw “System.NotImplementedException” with the following error message.

The method or operation is not implemented.
Now let's see the code where I have extended the System.Web.Security.MembershipProvider class. Some of the methods which have not been implemented have been removed for brevity.

CustomMembershipProvider class
public class CustomMembershipProvider : System.Web.Security.MembershipProvider 
{ 
    public CustomMembershipProvider() : base() 
    { 
    }

    public override string ApplicationName 
    { 
        get 
        { 
            return "Authentication"; 
        } 
        set 
        { 
            throw new NotImplementedException(); 
        } 
    } 
    //Non implemented methods have been removed for brevity. 
    public override bool ChangePassword(string username, string oldPassword, string newPassword) 
    { 
        WebServiceAuthentication wsa = new WebServiceAuthentication(); 
        return wsa.ChangePassword(username, newPassword); 
    }

    public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) 
    { 
        MembershipUser cust = this.GetUser(username, false);       

        if (cust == null) 
        { 
            WebServiceAuthentication wsa = new WebServiceAuthentication(); 
            cust = wsa.CreateUser(username, password, email); 
            status = cust != null ? MembershipCreateStatus.Success : MembershipCreateStatus.UserRejected; 
        } 
        else 
            status = MembershipCreateStatus.DuplicateUserName; 
        return cust; 
    }

    public override MembershipUser GetUser(string username, bool userIsOnline) 
    { 
        WebServiceAuthentication wsa = new WebServiceAuthentication(); 
        MembershipUser mu = wsa.GetUser(username); 
        return mu; 
    }

    public override int MinRequiredNonAlphanumericCharacters 
    { 
        get { return 0; } 
    }

    public override int MinRequiredPasswordLength 
    { 
        get { return 2; } 
    }

    public override bool RequiresQuestionAndAnswer 
    { 
        get { return false; } 
    }

    public override bool RequiresUniqueEmail 
    { 
        get { return false; } 
    } 
    //Non implemented methods have been removed for brevity

    public override void UpdateUser(MembershipUser user) 
    { 
        WebServiceAuthentication wsa = new WebServiceAuthentication(); 
        wsa.UpdateCustomer(user); 
    }

    public override bool ValidateUser(string username, string password) 
    { 
        WebServiceAuthentication wsa = new WebServiceAuthentication(); 
        if (wsa.ValidateUser(username, password)) 
        { 
            FormsAuthenticationTicket fat = new FormsAuthenticationTicket(1, username, DateTime.Now, DateTime.Now.AddMinutes(30), true, string.Empty); 
            HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(fat)); 
            HttpContext.Current.Request.Cookies.Add(authCookie); 
            return true; 
        } 
        else 
        { 
            return false; 
        } 
    } 
}

Open in new window


In the above code, I have extended the MembershipProvider class and overridden “ChangePassword”, “CreateUser”, “GetUser”, “UpdateUser” and “ValidateUser” methods.

All these methods are pretty straightforward.  The methods create an instance of our web service class and call the web methods to change password, create user etc.  The previously-mentioned membership methods are called by the various login controls provided in ASP.NET.  “ChangePassword” is called by the “ChangePassword” control when you click the “Change Password” button. “CreateUser” method of the MembershipProvider class is executed when you click the “Create User” button of “CreateUserWizard” control. “ValidateUser” method is called whenever the user request needs validation or when you click the “Log in” button of the “Log in” control.

Once you have extended the MembershipProvider class add the following tag in the web.config file of your website.

web.config
<membership defaultProvider="CustomProvider" > 
      <providers>        
        <add name="CustomProvider" type="CustomMembershipProvider" />        
      </providers>  
</membership>

Open in new window


In the above markup the "defaultProvider" is an optional parameter. If not provided it will default to "AspNetSqlProvider", the default provider provided by Microsoft. The "type" attribute has the name of the extended membership provider class. In our case, since the class is placed in the "App_Code" folder, we have only specified the class name. If your membership provider class lies in some different dll and you have added it as a reference to your website then one has to specify the full name preceded by the namespace as well. Also using the “add” tag in “providers” tag you can set the membership properties like "connectionStringName", "enablePasswordRetrieval", "enablePasswordReset" etc in the. Sample tag is pasted below.

<connectionStrings>
    <add name="connStr" connectionString="Data Source=testserver;Initial Catalog=Northwind;Persist Security Info=True;User ID=sa;Password=sa"/>
</connectionStrings>
<!--Membership provider configuration with membership properties set in web.config. The connectionStringName should match with the one provided in you web.config.-->
<membership defaultProvider="CustomProvider" > 
      <providers>        
        <add name="CustomProvider" type="CustomMembershipProvider" applicationName="TestApp" connectionStringName="connStr" enablePasswordRetrieval="false" enablePasswordReset="true" maxInvalidPasswordAttempts="5"  />        
      </providers>  
</membership>

Open in new window


With this done, you can drag and drop the various ASP.NET login controls like the CreateUserWizard, ChangePassword, Login etc and you can create users, change password and login into the application without writing any code in the code behind files where these ASP.NET login controls are used.  Isn’t it so easy to use the various ASP.NET login controls once you have extended the MembershipProvider class.

The original article with comments and discussion can be found in my blog here.
http://sandblogaspnet.blogspot.com/2010/02/extending-membership-provider.html
3
Comment
Author:Sandeep P R
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
2 Comments
 
LVL 8

Expert Comment

by:PagodNaUtak
Thank you, very much for this tutorial, as a begginner in web development this article really help me alot.

Nice work!
0
 
LVL 9

Author Comment

by:Sandeep P R
Thanks. I am happy that it helped you a lot.
0

Featured Post

On Demand Webinar: Networking for the Cloud Era

Ready to improve network connectivity? Watch this webinar to learn how SD-WANs and a one-click instant connect tool can boost provisions, deployment, and management of your cloud connection.

Join & Write a Comment

In this video, Percona Director of Solution Engineering Jon Tobin discusses the function and features of Percona Server for MongoDB. How Percona can help Percona can help you determine if Percona Server for MongoDB is the right solution for …
Want to learn how to record your desktop screen without having to use an outside camera. Click on this video and learn how to use the cool google extension called "Screencastify"! Step 1: Open a new google tab Step 2: Go to the left hand upper corn…

Keep in touch with Experts Exchange

Tech news and trends delivered to your inbox every month