Monday, February 14, 2011

Securing your LDAP Membership Provider

There are situations where you want to have Forms based Authentication in SharePoint. That is when you will choose the Claims based Authentication on the Web Application.

We had a requirement where we need to classify two kinds of users Employees and Clients. For a certain reason we had to chose Active Directory as the user store for both kinds of users. We could not reuse the Active Directory of the Employees for the Client users. Hence, we ended creating another domain just for the client users.

When you are using a LdapProvider if the Application Pool account do not have the rights to perform an LDAP request on the Active Directory then you will need to specify two attributes connectionUsername & connectionPassword in the Ldap membership provider of the web.config. This is where you would not want to keep the connectionPassword in plain text. Below I have given a simple implementation to Encrypt and store the password and how you could use a method to retrieve the password at run time.

The web.config of the Web Application, Central Administration & Security Token Service will look something like this with a connectionPassword having the encrypted string.

<membership defaultProvider="CustomLdapProvider">

<providers>

<add name="ClientsADMembershipProvider" type="Microsoft.Office.Server.Security.LDAPMembershipProvider, Microsoft.Office.Server, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71E9BCE111E9429C" server="dc.clientsdomain.com" port="389" useSSL="false" userDNAttribute="distinguishedName" userNameAttribute="sAMAccountName" userContainer="CN=Users,DC=domain,DC=com" userObjectClass="person" userFilter="(|(ObjectCategory=group)(ObjectClass=person))" scope="Subtree" otherRequiredUserAttributes="sn,givenname,cn" connectionUsername="clientsdomain\administrator" connectionPassword="SDJFSew98234DFJ889==" />

<add name="EmployeesADMembershipProvider" type="Microsoft.Office.Server.Security.LDAPMembershipProvider, Microsoft.Office.Server, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71E9BCE111E9429C" server="dc.employeesdomain.com" port="389" useSSL="false" userDNAttribute="distinguishedName" userNameAttribute="sAMAccountName" userContainer="CN=Users,DC=domain,DC=com" userObjectClass="person" userFilter="(|(ObjectCategory=group)(ObjectClass=person))" scope="Subtree" otherRequiredUserAttributes="sn,givenname,cn" connectionUsername="employeesdomain\administrator" connectionPassword="SL43Sew982342KLSDF==" />

<add name="CustomLdapProvider" type="Project.CustomLdapProvider, Project, Version=1.0.0.0, Culture=neutral, PublicKeyToken=8027a17523a78ae328" />

</providers>

</membership>


The partial implementation of CustomLdapProvider will be something like below:

public class CustomLdapProvider : MembershipProvider
    {
        private static LdapMembershipProvider _employeesProvider = null;
        private static LdapMembershipProvider _clientsProvider = null;
        private LdapMembershipProvider GetMembershipProvider(string providerName)
        {
            LdapMembershipProvider provider = new LdapMembershipProvider();

            // In SharePoint when your login page is coming from Layouts folder, HttpContext.Current is returning null. Hence the next line.
            //Configuration config = WebConfigurationManager.OpenWebConfiguration(HttpContext.Current.Request.ApplicationPath);

Configuration config = WebConfigurationManager.OpenWebConfiguration(@"~/web.config");
            MembershipSection section = (MembershipSection)config.GetSection("system.web/membership");
            ProviderSettings providerSettings = section.Providers[providerName];
            NameValueCollection param = providerSettings.Parameters;
            param["connectionPassword"] = Utility.Decryption(param["connectionPassword"], true);
            provider.Initialize(providerName, param);
            return provider;
        }
        private MembershipProvider EmployeesProvider
        {
            get
            {
                if (_employeeProvider == null)
                {
                    _employeeProvider = GetMembershipProvider("EmployeesADMembershipProvider");
                }
                return (MembershipProvider)_employeesProvider;
            }
        }
        private MembershipProvider ClientsProvider
        {
            get
            {
                if (_clientsProvider== null)
                {
                    _clientsProvider = GetMembershipProvider("ClientsADMembershipProvider");
                }
                return (MembershipProvider)_clientsProvider;
            }
        }
// Override all the membership provider and use the appropriate Membership provider to call the overloads.
public override bool ValidateUser(string name, string password)
        {
                 // name = loginid@clientsdomain.com / loginid@employeesdomain.com
                 // Extract the domain name and based on the domain connect to the appropriate Membership Provider (EmployeesProvider, ClientsProvider) and call the ValidateUser.
                // Ex: ClientsProvider.ValidateUser(name, password)
        }
}

I am sure this will be quite useful in implementing Forms Based Authentication in SharePoint when you have to work against an LDAP Provider and do not want to compromise on the connection string.

3 comments:

  1. Hi,

    when are we use ClientsProvider.ValidateUser(name, password) method ? i mean i have no custom login page ? how can we call the method validate ??

    thanks inadvance.

    ReplyDelete
  2. Hi how did you implement this Custom provider in Sharepoint?

    I mean how did you "install" this class

    ReplyDelete
  3. It was part of one of the binaries that you will deploy in the GAC. If you see the CustomLdapProvider config entry it is clear that it is part of the Project.dll.

    ReplyDelete