DevLearningTools

MODULE 14 · LESSON 07

cfldap

Connecting to an LDAP directory server like Active Directory with cfldap: querying entries, authenticating a user against a company directory instead of your own table, and preventing LDAP injection with encodeForLDAP.

New lessons are added one at a time as the course gets built out — a graded quiz for each lesson is still on the way.

The authentication lesson checked a submitted username and password against your own users table. A lot of real enterprise applications don't have their own user table at all, they authenticate against a company's existing directory server, commonly Microsoft Active Directory, over LDAP (Lightweight Directory Access Protocol). cfldap is ColdFusion's interface to that.

Learning Objectives

After completing this lesson, you'll be able to:

  • Explain what an LDAP directory and a distinguished name (DN) actually are.
  • Query directory entries with cfldap action="query".
  • Authenticate a user against Active Directory instead of your own table.
  • Add, modify, and delete directory entries.
  • Prevent LDAP injection in a filter built from user input with encodeForLDAP().

What an LDAP Directory Actually Is

An LDAP directory is a hierarchical database of entries, think of it like a folder tree, where every entry (a user, a group, a computer) has a unique path called a distinguished name (DN), such as cn=John Smith,ou=Sales,dc=example,dc=com. Reading that right to left: dc=example,dc=com is the domain, ou=Sales is an organizational unit (a folder) inside it, and cn=John Smith is the specific entry.

How a cfldap Query Flows

Build the Filter

e.g. (sAMAccountName=jsmith)

cfldap Connects

server, port, credentials

Directory Searches

starting at the DN in start=

Entries Come Back

as a normal CFML query

Querying Directory Entries

Tag Syntax
<cfldap server="ldap.example.com"
    port="389"
    action="query"
    name="results"
    start="dc=example,dc=com"
    scope="subtree"
    filter="(sn=Smith)"
    attributes="cn,sn,givenName,mail,title">

<cfoutput query="results">
    #cn# - #mail#<br>
</cfoutput>

Key Attributes

AttributeMeaning
serverHostname or IP of the directory server (required)
portDefault 389, or 636 for LDAPS (SSL)
actionquery, add, modify, modifyDN, or delete
startThe DN to begin searching from (required for query)
scopeonelevel (default), base, or subtree
filterSearch criteria, e.g. "(sn=Smith)"
attributesComma-delimited list of attributes to return, or "*" for all
secure"CFSSL_BASIC" enables SSL-encrypted authentication
timeoutMilliseconds to wait before failing (default 60000)

A Real Example: Authenticating Against Active Directory

Instead of hashing a password and comparing it to your own users table, the query itself IS the authentication check here: if the bind (the connection using the submitted username and password) succeeds and a matching entry comes back, the credentials were correct. This is Adobe's own documented pattern for checking a login against Active Directory.

CFScript
cfldap(
    server = "ldap.mycompany.com",
    port = 636,
    action = "QUERY",
    name = "qLDAPLookup",
    secure = "CFSSL_BASIC",
    username = "MYDOMAIN\" & encodeForLDAP(arguments.username),
    password = arguments.password,
    start = "dc=mycompany,dc=com",
    attributes = "cn,userPrincipalName,title,mail",
    filter = "(sAMAccountName=" & encodeForLDAP(arguments.username) & ")"
);

if (qLDAPLookup.recordCount EQ 1) {
    // Credentials were valid, the bind succeeded and found exactly one matching user
    cfloginuser(name = arguments.username, password = arguments.password, roles = "employee");
}
NOTE

encodeForLDAP() around any user-supplied value going into a filter is not optional, it's the direct equivalent of cfqueryparam for SQL: without it, someone could inject LDAP filter syntax through the username field.

Adding, Modifying, and Deleting Entries

actionRequired Attributes
adddn, attributes (the new entry's data)
modifydn, attributes, modifytype (add, delete, or replace)
modifyDNdn (changes an entry's distinguished name/location)
deletedn (the entry to remove)
Tag Syntax
<cfldap action="modify"
    server="ldap.example.com"
    dn="cn=John Smith,ou=Sales,dc=example,dc=com"
    attributes="title=Senior Sales Manager"
    modifytype="replace">

Handling Special Characters in Attribute Values

When an attribute value itself contains a comma or semicolon, those characters collide with the characters cfldap normally uses to separate multiple values and multiple attribute pairs. separator and delimiter let you pick different characters instead, exactly for this case, per Adobe's own advanced topics documentation.

Tag Syntax
<cfldap action="add"
    server="ldap.example.com"
    dn="cn=Example Inc,ou=Companies,dc=example,dc=com"
    attributes="o=Example, Inc.|description=Sales; Marketing"
    separator="|">
NOTE

That's a distinct concern from encodeForLDAP(): separator/delimiter reshape how attribute values themselves are parsed, while encodeForLDAP() escapes user input so it can't be interpreted as LDAP filter syntax at all.

Common Beginner Mistakes

Building a filter by concatenating raw user input

filter = "(sAMAccountName=" & arguments.username & ")" without encodeForLDAP() is vulnerable to LDAP injection, the same class of problem as building SQL without cfqueryparam. Always encode user-supplied values going into a filter.

Forgetting that scope="onelevel" only searches one level deep

onelevel searches only the immediate children of start, not the full tree below it. subtree searches everything underneath, which is usually what a "find this user anywhere in the directory" query actually needs.

Assuming a failed bind throws a catchable exception automatically

Wrap authentication attempts in cftry/cfcatch, an invalid username/password combination during the connection itself typically surfaces as an exception rather than an empty query result.

Using plain LDAP (port 389) to send credentials in production

Port 389 is unencrypted. secure="CFSSL_BASIC" with port 636 (LDAPS) encrypts the connection, sending a real password over plain LDAP exposes it on the network exactly like plain HTTP would.

Best Practices

  • Always wrap user input going into a filter with encodeForLDAP().
  • Use secure="CFSSL_BASIC" (port 636) rather than plain LDAP for any connection carrying credentials.
  • Use scope="subtree" when searching needs to cover an entire branch, not just its immediate children.
  • Wrap cfldap authentication calls in cftry/cfcatch, since a failed bind commonly throws rather than returning an empty result.

Interview Questions

How would you authenticate a user against Active Directory instead of your own database?

Run a cfldap query using the submitted username and password as the bind credentials, with a filter matching that username (e.g. sAMAccountName). If the bind succeeds and returns a matching entry, the credentials were valid, the directory server did the actual password verification, not your code.

What does encodeForLDAP() protect against, and why does it matter?

LDAP injection, where unescaped user input in a filter string gets interpreted as LDAP filter syntax instead of a literal value, potentially letting an attacker manipulate the search logic (similar in spirit to SQL injection). It's the same category of fix as cfqueryparam, applied to LDAP filters instead of SQL.

What's the difference between scope="onelevel" and scope="subtree"?

onelevel searches only the immediate children of the start DN. subtree searches that entire branch recursively, every level beneath it. A search meant to find an entry anywhere in a organizational unit's structure needs subtree, not onelevel.

Summary

In this lesson, you queried an LDAP directory with cfldap, authenticated a user against Active Directory by using their submitted credentials as the bind itself, added/modified/deleted directory entries, and covered a real security practice: encoding user input with encodeForLDAP() before it goes into a filter, to prevent LDAP injection.

What's Next?

The next lessons cover cfcollection, cfindex, and cfsearch: building and querying a full-text search collection directly from ColdFusion.