LDAP Integration in Python — Core Concepts

What LDAP does

LDAP (Lightweight Directory Access Protocol) is a protocol for reading and writing data in a directory service. Think of it as a specialized database optimized for frequent reads and infrequent writes — perfect for storing organizational data like user accounts, group memberships, and access permissions.

Active Directory, the identity backbone of most enterprises, speaks LDAP. So do OpenLDAP, FreeIPA, and 389 Directory Server. If your Python app needs to authenticate against a corporate directory, you’re almost certainly talking LDAP.

The directory tree (DIT)

LDAP organizes data in a tree structure called the Directory Information Tree. Every entry has a Distinguished Name (DN) — a unique path from the entry to the root:

dc=example,dc=com                    (root)
├── ou=People                        (organizational unit)
│   ├── uid=jsmith                   (user entry)
│   └── uid=mjones                   (user entry)
└── ou=Groups
    ├── cn=engineering               (group entry)
    └── cn=marketing                 (group entry)

A user’s full DN might be uid=jsmith,ou=People,dc=example,dc=com. The DN is how you reference specific entries in the tree.

Core operations

Bind — authenticate to the LDAP server. Simple bind sends a DN and password. This is also how you verify user credentials: try to bind as the user, and if it succeeds, the password is correct.

Search — find entries matching criteria. You specify a base DN (where to start), a scope (how deep to search), and a filter (what to match).

Modify — change attributes on an existing entry. Add, remove, or replace values.

Add/Delete — create or remove entire entries.

Python integration with ldap3

The ldap3 library is pure Python (no C dependencies), supports Python 3, and handles connection pooling, TLS, and paged results.

from ldap3 import Server, Connection, ALL, SUBTREE

# Connect to the directory
server = Server("ldap://ldap.example.com", get_info=ALL)
conn = Connection(server, user="cn=admin,dc=example,dc=com",
                  password="admin_password", auto_bind=True)

# Search for a user
conn.search(
    search_base="ou=People,dc=example,dc=com",
    search_filter="(uid=jsmith)",
    search_scope=SUBTREE,
    attributes=["cn", "mail", "memberOf"],
)

for entry in conn.entries:
    print(entry.cn, entry.mail)

Authenticating users (bind check)

The standard pattern for user authentication: first search for the user’s DN using a service account, then attempt to bind with the user’s credentials.

def authenticate(username, password):
    # Step 1: Find the user's DN
    conn.search("ou=People,dc=example,dc=com",
                f"(uid={username})", attributes=["dn"])
    if not conn.entries:
        return False

    user_dn = conn.entries[0].entry_dn

    # Step 2: Try binding as the user
    user_conn = Connection(server, user=user_dn, password=password)
    return user_conn.bind()

Search filters

LDAP filters use a prefix notation with operators:

  • (uid=jsmith) — exact match
  • (cn=John*) — wildcard
  • (&(objectClass=person)(department=Engineering)) — AND
  • (|(mail=*@example.com)(mail=*@corp.com)) — OR
  • (!(accountDisabled=TRUE)) — NOT

Common misconception

“LDAP is outdated and replaced by modern identity providers.” LDAP isn’t going anywhere. Active Directory — the world’s largest identity system — is fundamentally an LDAP directory. Modern IdPs like Okta and Azure AD still sync from or proxy to LDAP backends. If you work in enterprise software, you’ll encounter LDAP.

The one thing to remember: LDAP integration in Python means connecting to a directory tree, searching for user entries by their attributes, and verifying credentials through bind operations — all handled cleanly by the ldap3 library.

pythonsecurityauthenticationenterprise

See Also