The Grayzone

Combined AD authentication with Umbraco

I’ve recently been looking at authentication in Umbraco. In particular using a combination of AD Authentication and the default Umbraco Membership Provider.

The Problem

I have a requirement to use multiple different MembershipProviders within one single Umbraco site. When a user logs in to Umbraco, I need to first try and authenticate them via Active Directory, and if this fails authenticate them with the Umbraco Membership Provider. There is also a requirement to use Umbraco’s Public Access functionality with Role base protection, so a new RoleProvider was also required.

The Solution

I had a look around the Umbraco forums and blogs and a popular way to approach this kind of situations is to allow the users to enter their AD credentials, if they authenticate and aren’t in Umbraco already then create a Member in Umbraco with the same credentials. I didn’t like this approach as there is no need to duplicate the users between AD and Umbraco, and keeping these 2 stores in sync going forward could prove to be a headache.

Looking into the Umbraco source, everything is driven off of the default Membership and Role Providers, as specified in the web.config. As an example the Public Access Functionality uses the following 2 methods:

Membership.GetUser()
Role.GetRolesForUser()

I was originally going to use 2 Membership Providers (AD + Umbraco) but since there is no way to influence which one would be picked I decided to create a proxy membership provider. This would be set as the default provider so that all calls are routed through it and would then delegate the work off to the appropriate place.

AD Membership Provider

At first I just setup an ActiveDirectoryMembershipProvider in the web.config, this was just a case of adding the appropriate LDAP connection string and declaring a new provider:

<connectionStrings>
  <add name="LDAPConnection" connectionString="LDAP://corp.domain.com" />
</connectionStrings>

<membership defaultProvider="UmbracoMembershipProvider" userIsOnlineTimeWindow="15">
  <providers>
  <!--Default Umbraco Providers-->
  <add name="AspNetActiveDirectoryMembershipProvider"
    type="System.Web.Security.ActiveDirectoryMembershipProvider, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
    connectionStringName="LDAPConnection"
    connectionUsername="MyDomain\username"
    connectionPassword="superSecret"
    connectionProtection="None"/>
  </providers>
</membership>

Proxy Membership Provider

I then created a new MembershipProvider to act as a proxy, using the ActiveDirectoryMembershipProvider or the UmbracoMembershipProvider, depending on where the users’ credentials are stored.

using System.Web.Security;

namespace TheGrayZone.Membership
{
  public class ProxyMembershipProvider : MembershipProvider
  {
    // Internal MembershipProvider
    private MembershipProvider _provider;
  
    // MembershipProvider keys
    private const string ADProviderName = "AspNetActiveDirectoryMembershipProvider";
    private const string UmbracoProviderName = "UmbracoMembershipProvider";

    public MembershipProvider Provider
    {
      get
      {
        if (_provider == null)
        {
          // Implement logic to decide which provider to use
          _provider = Membership.Providers[UmbracoProviderName];
        }
        return _provider;
      }
    }

    public override void UpdateUser(MembershipUser user)
    {
      Provider.UpdateUser(user);
    }  

    /* *Implement all MembershipProvider methods* */

    public override bool ValidateUser(string username, string password)
    {
      // Crude setup of which provider to use. First try AD then Umbraco
      _provider = Membership.Providers[ADProviderName];
      if (_provider.ValidateUser(username, password))
        return true;
      else
      {
        _provider = Membership.Providers[UmbracoProviderName];
        if (_provider.ValidateUser(username, password))
          return true;
      }

      _provider = null;
      return false;
    }
  }
}

this is then set as the default provider in the web.config by using:

<membership defaultProvider="ProxyMembershipProvider" userIsOnlineTimeWindow="15">
  <providers>
  <!--Default Umbraco + AD Providers -->
    <add name="ProxyMembershipProvider" type="TheGrayZone.Membership.ProxyMembershipProvider" />
  </providers>
</membership>

Using this implementation means that the calling code can just call methods on the default membership provider and it will know which ‘real’ provider to use. Authenticating the user from the login form now just becomes a case of using the following code (or you can use the built in ASP.NET Login controls and this will happen automatically):

using System.Web.Security;

public bool AuthenticateUser(string userName, string password)
{
  // Validate with Proxy, don't care if it's AD or Umbraco
  if (Membership.ValidateUser(userName, password))
  {
    FormsAuthentication.SetAuthCookie(userName, false);
    return true;
  }
  
  // Not authenticated
  return false;
}

Role Provider

Since one of the requirements was for Umbraco’s Public Access to work I also needed to create a custom RoleProvider. Internally Umbraco uses the default RoleProvider to determine if a user was in a particular role, and grants/denies access depending on the result of this call. Creating the RoleProvider is similar to creating a MembershipProvider, inheriting from the RoleProvider base class and implementing any custom logic within.

using System.Web.Security;

namespace TheGrayZone.Membership
{
  public class ProxyRoleProvider : RoleProvider
  {
    /* *Implement all RoleProvider methods* */
  }
}
<roleManager enabled="true" defaultProvider="ProxyRoleProvider">
  <providers>
    <clear />
    <!-- Default Umbraco + AD Providers -->
    <add name="ProxyRoleProvider" type="TheGrayZone.Membership.ProxyRoleProvider"/>
  </providers>
</roleManager>

Share this: