Easy Mixed-Mode authentication implementation in ASP.NET

Recently I had to change our intranet authentication, which was hitting Membership database on SQL Server, to validate against Active Directory (AD). However, I only needed to authenticate against AD. I still wanted to use the Membership database for roles security. Turns out this is pretty easy and here is the steps i took to set this up.

Configure connection strings for Membership and Directory Services (LDAP).

<connectionStrings>  
  <add name="Membership.ConnectionString.SQL" connectionString="Data Source=./sqlexpress;Initial Catalog=AspNetDB; uid=userid; pwd=password"/>
  <add name="Membership.ConnectionString.LDAP" connectionString="LDAP://dc=test,dc=mircosoft,dc=com"/>
</connectionStrings>  

Configure application to use Forms Authentication.

<authentication mode="Forms">  
  <forms name="WEBPORTAL.ASPXAUTH" loginUrl="~/Login.aspx" defaultUrl="~/Landing.aspx" path="/" />
</authentication>  

Only allow authenticated users to have access.

<authorization>  
  <deny users="?"/>
  <allow users="*"/>
</authorization>  

Configure SQL and Active Directory Membership Providers.

<membership hashAlgorithmType="MD5" defaultProvider="AspNetSqlMembershipProvider">  
  <providers>
    <clear/>
    <add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="Membership.ConnectionString.SQL" applicationName="/" />
    <add name="ActiveDirectoryMembershipProvider" type="System.Web.Security.ActiveDirectoryMembershipProvider, System.Web, Version=2.0.0.0,Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" connectionStringName="Membership.ConnectionString.LDAP" attributeMapUsername="SAMAccountName" connectionUsername="domain\administrator" connectionPassword="youradminpassword" />
  </providers>
</membership>  

ASP.NET Provider models allows you to dynamically swap providers, which I will show later. We configure both AD and SQL Membership providers. We will use AD provider to login, but we set the default provider to SQL Membership because we will rely on it to create us an authentication ticket.

Protect our landing page from unauthorized users.

<location path="Account/Landing.aspx">  
  <system.web>
    <authorization>
      <allow roles="Registered User"/>
      <deny users="*"/>
      <deny users="?"/>
    </authorization>
  </system.web>
</location>  

Create Login.aspx page and add the Login control to it.

<asp:Login  
   ID="NetworkLogin"
   runat="server"
   EnableViewState="false"
   RenderOuterTable="false"
   OnAuthenticate="NetworkLogin_Authenticate">
</asp:Login>  

We will provide our own authentication logic, so we hook into the OnAuthenticate event of the Login control.

Specify authentication logic to authenticate against Active Directory.

protected void NetworkLogin_Authenticate(object sender, AuthenticateEventArgs e)  
{
    if (Membership.Providers["ActiveDirectoryMembershipProvider"].ValidateUser(NetworkLogin.UserName, NetworkLogin.Password))
    {
        if (Membership.GetUser(NetworkLogin.UserName) != null)
        {
            e.Authenticated = true;
        }
    }
}

The Membership class has a nice collection of all providers registered in the config. We pick the AD provider from the collection and use its ValidateUsers method to validate logins. If a login is successful, SQL Membership provider kicks in as the default provider and creates an authentication ticket for the authorized username.

This is where you have to be careful, because you need to make sure that the username specified at the login exists in the Membership database under the application name specified in your SQL Membership provider configuration. In this case it’s the root application (“/”). So a nice check in the above method would be nice to make sure the user actually exists in SQL Membership database.

That’s basically it. We can now authenticate with Active Directory and continue using the benefits of SQL Membership role-based security.

Update

As was pointed out in the comments, if your AD service is using SSL, you have to set connectionProtection attribute to ‘Secure’ in your LDAP provider configuration. In addition, some tests revealed that AD provider has issues with losing connection and not being able to reestablish it (or at least this is how i remember this issue… it’s been a while). To avoid this problem, you must specify the port number in your LDAP connection string. The port number for LDAP is 389. If you’re using LDAP with SSL, it’s 636.