Create a New Custom Membership Provider for SharePoint 2013

Building up on last week’s article where I explained how you can use PowerShell to automate the configuration of a SharePoint Web Application that uses Form-Based Authentication, this article aims at giving you the steps to develop your own custom Membership provider. The requirement to build a custom membership provider came up this week while I was in Vancouver at a customer’s site. Basically the customer already had a custom database where they stored users’ credentials for some of their Line of Business Applications, and they wanted to reuse this information instead of recreating the aspnetdb database as described in my previous article.

The scenario here is that we want to allow the organization to create user accounts in a custom database table (SQL Server, Oracle, etc.), and have SharePoint authenticate against these custom credentials. The example given in my blog post assumes that you have such a database already created in your organization’s environment. In my case, my database is located locally (I am using a Single Server Farm for demo purposes). My table is simply made off 4 columns:

  • UserId: the unique identifier of the user’s record
  • UserName
  • Password
  • UserEmailAddress

SQLOM

Yes, you are correct, I am storing my passwords in Clear Text in my database….what is wrong with that?!? Just kidding, of course in a production environment you’ll want to encrypt your passwords so that they are not directly exposed to anybody having access to your environment. However, in order to keep my example simple, I went ahead and simply stored them in clear text to better illustrate how the process works.

Create the Custom Membership Provider’s Assembly

The first thing you need to do is open Visual Studio and create a new Class Library project. In my case the project will be named NikCustomProvider, and I will be building it on top of the .NET 2.0 Framework (to keep thing simply when it comes to the GAC).
ClassLibrary
In my case, I will be renaming the Class1.cs file that gets created by default in Visual Studio to “MembershipProvider”. In your project, add a reference to the System.Web, System.Web.ApplicationServices and to the System.Configuration .NET Framework assemblies.
SystemWebRef
Specify that your class will be using the System.Web.Security Namespace in your class’s headers.
Using
The next step is to declare that your class will be inheriting from the SQLMembershipClass.
InheritSystemWebSecurity
Inheriting this class will allow you to override 6 very important methods:

  • CreateUser: Which allows you to add new users into your custom SQL database through IIS or any other method that allows you to leverage your Custom Membership Provider. Note:: if you have a custom application that takes care of creating your users in your custom database, you may not need to override this method at all in your code. In our current example we will simply implement this method by throwing out a NotImplementedException;
  • ValidateUser: This method takes in a username and a password, and ensures that the user exists in the custom database and that the credentials are valid;
  • GetAllUsers: Allows you to retrieve all users from your custom database;
  • GetUser: Which gets a specific user from the database;
  • UpdateUser: Which updates a specific user record in your custom Database. Note: will simply return a NotImplementedException in our case.
  • DeleteUser: Which deletes a specific user record in your custom Database. Note: will simply return a NotImplementedException in our case.

Override the GetAllUsers Method

As mentioned above, the GetAllUsers method is the method that will allow your custom Membership provider to retrieve a list of all users. Be it for IIS trying to display a list of them under the .NET Users module, or SharePoint trying to retrieve a full list of users available when you are trying to grant permissions to items, this method is very important. This method allows you to do pagination if you need to obtain users back from the database in smaller batches (instead of getting them all back at once which can impact performance). Again, for the sake of simplicity, we will simply ignore this option and get a full list of all user back. Your method needs to return a MembershipUserCollection object, and requires you to set the totalRecords variable before returning this value. That variable represents the total number of user entries that are about to be returned in your MembershipUserCollection object. If you use pagination, make sure you don’t have this variable set to reflect the total number of users in your Database, but rather the number you have in the batch returned.

My method will simply go, query my custom SQL database table and return all results back from it (SELECT * type of query). Here is the full code for my method:

public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
        {
            System.Data.SqlClient.SqlConnection conn = new System.Data.SqlClient.SqlConnection("Server=WIN-6O74I1M7M69;Database=Demos;Integrated Security=true");
            conn.Open();
            System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand("SELECT * FROM Users", conn);
            System.Data.SqlClient.SqlDataReader reader = cmd.ExecuteReader();

            MembershipUserCollection members = new MembershipUserCollection();
            int totalUsers = 0;
            while (reader.Read())
            {
                members.Add(new MembershipUser(providerName: "NikCustomProvider",
                name: reader["UserName"].ToString(),
                providerUserKey: null,
                email: reader["UserEmailAddress"].ToString(),
                passwordQuestion: "",
                comment: "",
                isApproved: true,
                isLockedOut: false,
                creationDate: DateTime.UtcNow,
                lastLoginDate: DateTime.UtcNow,
                lastActivityDate: DateTime.UtcNow,
                lastPasswordChangedDate: DateTime.UtcNow,
                lastLockoutDate: DateTime.UtcNow));
                totalUsers++;
            }
            totalRecords = totalUsers;

            conn.Close();
            return members;
        }

Override the GetUser Method

The second method we need to implement is the method that will allow us to retrieve a specific user out of our database. Now, when dealing with SharePoint, this method is called whenever you try to search for a user in the people picker or people directory control. What you really want to do here to implement some sort of a wild card search query back to your custom Database. What I mean by this is that if you were to simple declare a SELECT statement and return object that would be an exact match on the username, the user will need to type in the username exactly as it is in the database in order to retrieve any users. For example, if my username was “Nikolas”, and in the people picker I’d type in “Nik”, my user would never be retrieved back. SharePoint wouldn’t even be able to retrieve suggestions that may match my query. Instead what you really wish to do is implement your SQL query using a LIKE statement to allow SharePoint to retrieve users out of your custom SQL Database based on username patterns.

My code simply uses the received username (or username pattern, remember “Nik” vs “Nikolas”) and will return a MembershipUser object back. Here is the full code for my method:

public override MembershipUser GetUser(string username, bool userIsOnline)
         {
            try
            {
                System.Data.SqlClient.SqlConnection conn = new System.Data.SqlClient.SqlConnection("Server=WIN-6O74I1M7M69;Database=Demos;Integrated Security=true");
                conn.Open();
                System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand("SELECT * FROM Users Where UserName LIKE '" + username + "'", conn);
                System.Data.SqlClient.SqlDataReader reader = cmd.ExecuteReader();
                MembershipUser user = null;
                if(reader.Read())
                {
                    user = new MembershipUser(
                    providerName: "NikCustomProvider",
                    name: username,
                    providerUserKey: null,
                    email: reader["UserEmailAddress"].ToString(),
                    passwordQuestion: "",
                    comment: "",
                    isApproved: true,
                    isLockedOut: false,
                    creationDate: DateTime.UtcNow,
                    lastLoginDate: DateTime.UtcNow,
                    lastActivityDate: DateTime.UtcNow,
                    lastPasswordChangedDate: DateTime.UtcNow,
                    lastLockoutDate: DateTime.UtcNow);
                }
                conn.Close();
                return user;
            }
            catch {}
            return null;
        }

Override the ValidateUser Method

This method is where all the actual security checks happen. It is responsible to receive the username and password entered by the user on the Form-Based login form and to ensure they match the records in our custom Database. If you were to be bold and simply want your users to connect with their usernames without having to pass in any password whatsoever, you could set this method to simply return true and that way users will always be granted access to your environment, doesn’t matter if they entered a valid password or not. Not sure this is really what we want on a production system right? So my method here will simply do a select query against my custom Database using both the received username and password in my where clause. If the query returns an entry, then my method will return true, otherwise it will return false. Now, you may be asking yourself “What about if the database returns more than one entry?”. My answer to you is that you probably screwed up somewhere in your custom Database. Unless you have a very good business rational to use a custom Database that allows for multiple entries using the same username for authentication, the system you use to manage the credentials stored in your database should ensure that the user name are unique. Think of your username as a Primary Key. It is the identifier for your records, and you should not allow two entries to have the same key.

Here is the full code for my method:

public override bool ValidateUser(string username, string password)
        {
            try
            {
                System.Data.SqlClient.SqlConnection conn = new System.Data.SqlClient.SqlConnection("Server=WIN-6O74I1M7M69;Database=Demos;Integrated Security=true");
                conn.Open();
                System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand("SELECT * FROM Users Where UserName = '" + username + "' AND Password='" + password + "'", conn);
                System.Data.SqlClient.SqlDataReader reader = cmd.ExecuteReader();
                if (reader.Read())
                {
                    return true;
                }
                conn.Close();
            }
            catch { }
            return false;
        }

Ensure your Assembly has a Strong Name

In order to be able to deploy our assembly in the Global Assembly Cache (GAC), it requires a strong name. This can be done by simply signing the assembly. Right click on your project in Solution explorer (project not solution) and go properties. In the left, click on the “Signing” tab, and toward the bottom of the panel, check the “Sign the assembly” checkbox.

SignAssembly

Under the checkbox, click on the “Choose a strong name key file” drop down and select “New…”
New

A pop-up will appear asking you to provide a key file name. You can put in whatever you feel like. Make sure you uncheck the option to use a password to protect the key file and click OK.
SigningAss

Add your Custom Membership Provider Assembly to the GAC

Make sure you use Visual Studio to compile your assembly and that you compile it using the .NET 2.0 framework. Note that compiling it to .NET 4.0 would work as well, but you’ll need to use a different approach to GAC you DLL than the one I am using here which is simple to call the gacutil.exe tool. Open a new PowerShell session as administrator and run the following lines of PowerShell, making sure to update the paths to reflect your environment. In my case, in order to be able to get the gacutil.exe tool, I needed to download the Windows 7 SDK tools (https://www.microsoft.com/en-ca/download/details.aspx?id=8279).

set-location "C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin"
.\gacutil.exe /u "NikCustomProvider" /f
.\gacutil.exe /i "C:\Users\Nik\Documents\Visual Studio 2015\Projects\NikCustomProvider\NikCustomProvider\bin\Debug\NikCustomProvider.dll" /f
iisreset

Configure a Trusted Provider

Now that we have built our custom Membership Provider and that it has been made available globally in the Global Assembly Cache, we need to declare it as a trusted provider in the application.config file of the .NET framework in order to be able to consume it in our SharePoint Web Applications. In order to do this, you need to go and edit the application.config file found at %systemroot%\System32\inetsrv\config. Go ahead and open the file in notepad. Search for the following section:

<trustedProviders allowUntrustedProviders=”false”>

Within this section, simply add the following tag to register your custom assembly. Ensure you replace the namespace, class name, and token by yours:
<add type=”NikCustomProvider.MembershipProvider, NikCustomProvider, Version=1.0.0.0, Culture=neutral, PublicKeyToken=6f548d97bd0e3c37″ />

Save and close the application.config file. You are now allowed to use it as a custom provider within your web applications.

Modify your SharePoint web.config Files to Register the Custom Provider

To get more details on the “how” and “why”, please refer to my previous blog post SharePoint Form-Based Authentication with SQL using PowerShell. Basically what we need to do at this stage is modify the web.config of the following three SharePoint Web Application/Services to ensure they are given access to our custom Membership provider:

  • Your SharePoint Web Application that will be using Form-Based Authentication to connect;
  • Central Administration;
  • Secure Token Service (STS).

For each web.config, find the following section:

<membership defaultProvider=”i”>
<providers>

Within it, add the following entry to register your custom provider (note that my provider will use a Connection String named “Form Based Auth”, which I defined in my previous article):

<add name=”NikCustomProvider” type=”NikCustomProvider.MembershipProvider, NikCustomProvider, Version=1.0.0.0, Culture=neutral, PublicKeyToken=6f548d97bd0e3c37″ applicationName=”/” connectionStringName=”FormBasedAuth” enablePasswordReset=”false” enablePasswordRetrieval=”false” passwordFormat=”Clear” requiresQuestionAndAnswer=”false” requiresUniqueEmail=”true” />

Save each web.config once the modifications have been completed.

Configure your SharePoint Web Application’s Authentication Provider

The last step of our configuration involves changing the value of our Web Application’s Authentication Provider to point to our newly created custom Membership provider. Open Central Administration and navigate to the Manage Web Applications page. Select the Web Application you want to enable Form-Based Authentication on, and click on the “Authentication Providers” button in the ribbon.
AuthProvider

You will be presented with a dialog. Select the Zone on which you wish to enable FBA. In my case the “Default” zone.

Defaultauth

Scroll down to the FBA section and enter the name of the provider (e.g. NikCustomProvider)

cacustomprov

Click on the Save button to persist the changes.

Test your changes

Make sure your FBA users are given access to your site collection, and navigate to it in the browser. In my case, I will navigate to my FBA enabled Web Application “http://localhost:83”. When prompted to login, enter the credentials of a user stored in your custom Membership database.

Bob