Active Directory LDAP Authentication

I recently had a project for a client where I needed to authenticate with their implementation of Microsoft’s ADAM. Behind the scenes they are syncing their Active Directory as well as their SAP data with ADAM.

Using ColdFusion, I was able to to do something like the following:


<cfif Trim(selUsername) neq "" and Trim(selPassword) neq "">
<cfset SearchFilter="sAMAccountName=#selUsername#">
<cfset viewfieldlistLookup = "sAMAccountName,distinguishedName,cn,memberOf">
    <cftry>
      <cfldap action="QUERY"
		      name="qry_authenticate_user"
		      attributes="#viewfieldlistLookup#"
		      filter="#SearchFilter#"
		      sort="sn"
		      start="#LDAPBase#"
		      server="#LDAPServer#"
		      username="#selUsername#"
		      password="#selPassword#">
      <cfcatch type = "Any">
        <cfset err = "#err#
	<li>Unable to find that username and password.</li>
">
      </cfcatch>
    </cftry>
<cfelse>
  <cfset err = "#err#
	<li>Username and Password are required fields</li>
">
</cfif><strong>
</strong>
<cfif err eq "" and IsDefined( "qry_authenticate_user.recordcount" ) and qry_authenticate_user.recordcount gt 0>
  <cfset variables.IsValid = 1>
<cfelse>
  <cfset variables.IsValid = 0>
</cfif>
...

The above works because all users of this organization has read only and search access for ADAM.

Now here is the interesting thing. For a different client, they don’t have an implementation of MS’ ADAM directory so I’m tasked with authenticating directly with their Active Directory. This company does not give the user read only or search capability of their Active Directory. So, I ended up having to rewrite it a bit by doing the following:


<cfset variables.IsValid = 0>
<cfset err = "">

<cfif Trim(selUsername) neq "" and Trim(selPassword) neq "">
    <cfset SearchFilter="sAMAccountName=#selUsername#">
    <cfset viewfieldlistLookup =
"cn,displayName,distinguishedName,givenName,homeDirectory,name,objectClass,sAMAccountName,sn,mail">
    <cftry>
        <cfldap action="QUERY"
                name="qryCheckUser"
                attributes="#viewfieldlistLookup#"
                start="#LDAPBase#"
                server="#LDAPServer#"
                username="#LDAPUsername#"
                password="#LDAPPassword#"
                filter="#SearchFilter#">
                <cfif qryCheckUser.RecordCount gt 0>
                    <cfset curDisplayName = "#qryCheckUser.displayName#">
                    <cfldap action="QUERY"
                            name="qryAuthenticateUser"
                            attributes="#viewfieldlistLookup#"
                            start="#LDAPBase#"
                            server="#LDAPServer#"
                            username="#curDisplayName#"
                                password="#selPassword#">
                    <cfif qryAuthenticateUser.RecordCount gt 0>
                        <cfset variables.IsValid = 1>
                    <cfelse>
                        <cfset err = "#err#
	<li>Username and/or Password Failed</li>
">
                        <cfset variables.IsValid = 0>
                    </cfif>
                <cfelse>
                    <cfset err = "#err#
	<li>Username and/or Password Failed</li>
">
                    <cfset variables.IsValid = 0>
                </cfif>
        <cfcatch type = "Any">
            <cfset err = "#err#
	<li>Technical Error Connecting to LDAP Server.  Please notify IT.</li>
">
            <cfset variables.IsValid = 0>
        </cfcatch>
    </cftry>
<!---
    Here is a list of Active Directory errors you could optionally test for:
    525 - user not found
    52e - invalid credentials
    530 - not permitted to logon at this time
    532 - password expired
    533 - account disabled
    701 - account expired
    773 - user must reset password
--->
<cfelse>
    <cfset err = "#err#
	<li>Username and Password are required fields</li>
">
</cfif>

Because the user doesn’t have permission to query AD via LDAP, I must use a system account to look up that person to find out if they exist first. There is an interesting twist in this second example. After a long time searching, I gave up on trying to find out why I couldn’t pass their NT username (sAMAccountName) and their password like I did in the first example with ADAM. After much trial and error, the only way I figured out how to do it is by looking up their DisplayName and passing that along with their supplied password instead. I think looking up their Distinguished Name (DN) works as well.

I’m assuming that AD blocks the passing of their NT Username for some obscure reason or more likely I have made a mistake. I’ll look to others reading this post for any insight. The current code works but I’m not 100% sure it’s the best solution.

I suppose another option is to set NT Authentication as a requirement for the directory within IIS and handle it that way. A colleague showed me a quick example which seemed like a pretty graceful way to handle things. I just am unclear on the compatibility of this option with other browsers, Servers like Apache, as well as possible problems from remote connections via VPN.

So, there you go, a couple of examples of how I have used ColdFusion to connect to two different LDAP compliant directories.

I’m looking for suggestions to make this better as well as any feedback you might have regarding the use of NT Authentication at the Web server level instead.

5 Responses to “Active Directory LDAP Authentication”

  1. MrBuzzy Says:

    Howdy, I’m pretty sure you can get NT/AD Auth modules for Apache. Browsers like Firefox or Opera will understand and prompt for a user/password. I don’t think VPN will be a problem either.
    Generally when authentication is handled by the web server the entered password is not available to ColdFusion, you should check cgi.auth_type to ensure the webserver is only authenticating they way you want (negotiate or NTLM for example). Then, as you did above, you could use a system account (with the right permissions) to query the LDAP info you want.
    Just in case you don’t know, there is also the cfntauthenticate tag it can help if you don’t want web server auth, depending on your network trust, domains, etc.
    Awwww… no subscribe option on your blog post :(

  2. Vincent Collins Says:

    Thanks for the suggestions Buzzy,

    A good portion of my development has been non-Intranet related and on shared servers so I figured it would be more efficient for me to just keep with what I know how to do which was build my own user authentication schemes. I see the problems with this approach of course.

    As you and my colleague suggest, I should give NT Authentication a shot for closed systems like Intranets where I have more control over the server.

    Has anyone encountered any gotchas using NT Authentication?

    I’m using WordPress so If you post a comment, you should be subscribed to the post automatically. Or are you asking for something else?

  3. MrBuzzy Says:

    Happy to try to help!
    re: subscribing, although I filled in my details, I wasn’t notified of your reply. I was able to subscribe to your all comments RSS however. I have wordpress too, so I probably suffer the same problem. I’ll go back to a CF blog one day…

  4. Douglas Knudsen Says:

    I’ve done a lot of LDAP work using ColdFusion as well as Java. To me, it rawks. I never hooked it up with IIS though, IIRC you need the server with IIS to belong to the larger domain that the LDAP in your company serves, something my employer at the time would not do for security reasons, real or imaginary, not sure. With CF do the above coupled with application files, but this only secures CF files. Go one level higher to JRun, and you can secure ALL resources and CF can even use it. This is not hard, but if your LDAP is setup non-standard, you may have to write a custom login command….see JAAS.

    From my experience the trickiest part was knowing the LDAP structure. Using a tool like LDAPBrowser from Softerra really helps here. The DN will work in your case, it had better, the DN or DistinguishedName is unique to the resource. Typically you use the first trip to fetch the DN, second trip to bind.

    have fun!

    DK

  5. Dave M Says:

    I’ve used the SAMAccountName ID and password to access the AD LDAP.

    Here’s some syntax that’s worked. This was calling from an outside AD domain to a seperate AD domain (where no trust relationships existed):

    The syntax (using the C# LDAP objects) is:
    domain\userID.

    e.g.
    string aUserID = “PK0001″; // SAMAccountName
    string aPassword = “thePassWord”;
    string theLDAPPathToYourServer = “LDAP://internalserver.BearSterns.com”; // fully qualifed path to server
    string anLDAPDomain = “BearSterns”;
    string domainAndUsername = anLDAPDomian + @”\” + aUserID;

    DirectoryEntry entry = new DirectoryEntry(theLDAPPathToYourServer,domainAndUsername, aPassword);

    As well, I think I’ve seen cases where the Exchange ID syntax has worked (e.g. userid@domain) to identify the logon name.

Leave a Reply