How to Create Your Own Membership Provider Instead of Using SqlMembershipProvider

So you want to use the ASP.NET SQL Membership authentification system but you’re constrained by data access rules. Well you can create your own provider instead of using the default SqlMembershipProvider, no matter which way you access selected data.

For example, you are forced to use a webservice, or the database of users already exists. The following solution will allow you to handle such cases instead of appealing to SqlMembershipProvider.

Step 1: Identify data to be managed

Let’s consider the following data model, which we will assume is the data contract provided by a webservice:

   
public class User
{
	public long UserId { get; set; }
	public string FirstName { get; set; }
	public string LastName { get; set; }
	public string Email { get; set; }
	public string Password { get; set; }
	public string Address { get; set; }
	public string City { get; set; }
	public string ZipCode { get; set; }
	public string Country { get; set; }
	public bool IsActive { get; set; }
	public DateTime CreationDate { get; set; }
	public DateTime LastLoginDate { get; set; }
}

Let’s also consider the data access service, which contains this signature:

    
public class AuthenticationService : IDisposable
{
	public bool UserExists(string email, string password)
	{
		//implementation here
	}

	public User GetUser(string username)
	{
		//implementation here
	}

	public User GetUser(long userId)
	{
		//implementation here
	}

	public bool UpdateUser(User user)
	{
		//implementation here
	}

	public void Dispose()
	{
		//implementation here
	}
}

Step 2: Create a custom MembershipUser derived from the original MembershipUser​

As can be seen, this class has the contract “User” as a property.

    
public class CustomMemberShipUser : MembershipUser
{
	private readonly User _userData;

	public User UserData
	{
		get { return _userData; }
	}

	
	/// 
	/// Constructeur de la classe derivée du MemberShip
	/// 
	public CustomMemberShipUser(string providername, User userData) :
	base(providername,
		 userData.Email,
		 userData.UserId,
		 userData.Email,
		 string.Empty,
		 string.Empty,
		 true,
		 !userData.IsActive,
		 userData.CreationDate,
		 userData.LastLoginDate,
		 DateTime.Now,
		 DateTime.Now,
		 DateTime.Now)
	{
		this._userData = userData;
	}
}

Note that you will need to import “System.Web.Security” assembly.

I have used some data in the “User” contract to fill in required fields in the base class.

I have also decided that the username is the email “!userData.IsActive”, filled into the “isLockedOut” property in the base class.

Step 3: Create a custom MembershipProvider derived from the original MembershipProvider​

Note that you must implement all methods. Otherwise, you’ll need to create an override method and add in “throw new NotImplementedException();”.

    
public class CustomMemberShipProvider : MembershipProvider
{
	public override bool ValidateUser(string username, string password)
	{
		using (var service = new AuthenticationService())
		{
			return service.UserExists(username, password);
		}
	}

	public override MembershipUser GetUser(string username, bool userIsOnline)
	{
		using (var service = new AuthenticationService())
		{
			var user = service.GetUser(username);

			if (null != user)
				return new CustomMemberShipUser(Membership.Provider.ApplicationName, user);

			return null;
		}
	}

	public override void UpdateUser(MembershipUser userToUpdate)
	{
		var user = (CustomMemberShipUser) userToUpdate;
		using (var service = new AuthenticationService())
		{
			var result = service.UpdateUser(user.UserData);
			if (!result)
				throw new Exception("User has not been updated");
		}

	}

	public override string ApplicationName
	{
		get { return "MyAppMemberShip"; }
		set { throw new NotImplementedException(); }
	}

	public override bool ChangePassword(string username, string oldPassword, string newPassword)
	{
		throw new NotImplementedException();
	}

	///
	/// 
	/// all overrrided methods
	/// 
	/// 

}

Step 4: Define this custom MembershipProvider as the default Membership provider in your web.config as follows:​

<system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
    <authentication mode="Forms">
      <forms loginUrl="~/Home/DoLogin" timeout="2880"/>
    </authentication>
    <membership defaultProvider="MyAppMemberShip">
      <providers>
        <clear/>
        <add name="MyAppMemberShip" type="MemberShip.CustomMemberShipProvider, MemberShip"/>
      </providers>
    </membership>
</system.web>

Note that “MemberShip.CustomMemberShipProvider, MemberShip” is my assembly where i have written my provider

Step 5: As the classical SqlMembershipProvider, add the authentication mode (with the login page url) to your web.config: ​

<system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
    <authentication mode="Forms">
      <forms loginUrl="~/Home/DoLogin"/>
    </authentication>
    <membership defaultProvider="MyAppMemberShip">
      <providers>
        <clear/>
        <add name="MyAppMemberShip" type="MemberShip.CustomMemberShipProvider, MemberShip"/>
      </providers>
    </membership>
</system.web>

Step 6: Test your implementation!

Don’t forget to use the “FormsAuthentication” class in order to create the “.ASPXAUTH” cookie that is used by MembershipProvider to identify the user and manage the sign-out method. 🙂

    
public class HomeController : Controller
{
	public ActionResult DoLogin()
	{
		if (Membership.ValidateUser("myemail@gmail.com", "xxxxx"))
		{
			FormsAuthentication.SetAuthCookie("myemail@gmail.com", true);
			return Content("login success");
		}
		return Content("login error");
	}

	public ActionResult Index()
	{
		if (User.Identity.IsAuthenticated)
		{
			var user = (CustomMemberShipUser)Membership.GetUser();
			return Content("User connected!");
		}

		return RedirectToAction("DoLogin");
	}

	public void SignOut()
	{
		FormsAuthentication.SignOut();
	}

}

Here are the results after my demo execution:

capture_1_0.png

capture_2.png

Easy, isn’t it? 😉

Leave a Reply