Skip to content

LDAP Authentication — Complete Enterprise Directory Guide

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about LDAP Authentication. We cover key concepts, practical examples, and best practices to help you master this topic.

LDAP (Lightweight Directory Access Protocol) is a protocol for accessing and maintaining distributed directory information services, commonly used for centralized authentication against Active Directory, OpenLDAP, and other directory servers in enterprise environments.

What You'll Learn

By the end of this lesson, you will implement LDAP authentication against Active Directory and OpenLDAP, understand the directory information tree, construct proper LDAP queries, prevent LDAP injection, and handle LDAP search and bind operations.

Why It Matters

LDAP is the backbone of enterprise identity management. Most large organizations store user credentials in Active Directory or OpenLDAP. Durga Antivirus Pro integrates with corporate LDAP directories for enterprise customer SSO, allowing employees to authenticate with their existing corporate credentials.

Real-World Use

An employee signs into their company's expense reporting system. The system binds to the corporate Active Directory using a service account, searches for the user by their email, and then performs a bind as the user to verify their password. The expense tool never stores passwords.

LDAP Auth Flow

sequenceDiagram
    participant User
    participant App
    participant LDAP as LDAP Server (AD/OpenLDAP)

    User->>App: Enter username/password
    App->>LDAP: Bind (service account)
    LDAP-->>App: Bind successful
    App->>LDAP: Search for user (mail=user@company.com)
    LDAP-->>App: User DN: CN=Alice Smith,OU=Users,DC=company,DC=com
    App->>LDAP: Bind as user DN with user's password
    LDAP-->>App: Bind successful (password verified)
    App-->>User: Authenticated

LDAP Authentication (Python)

from ldap3 import Server, Connection, ALL, core
import ssl

class LDAPAuthenticator:
    def __init__(self, ldap_host, ldap_port=389, use_ssl=False, base_dn="DC=company,DC=com"):
        self.server = Server(
            ldap_host,
            port=ldap_port if not use_ssl else 636,
            use_ssl=use_ssl,
            get_info=ALL,
        )
        self.base_dn = base_dn

    def authenticate(self, username, password, search_filter=None):
        try:
            conn = Connection(
                self.server,
                user=self._get_service_account(),
                password=self._get_service_password(),
                auto_bind=True,
            )
            print(f"[LDAP] Connected to {self.server.host}")

            search_filter = search_filter or f"(mail={username})"
            conn.search(
                search_base=self.base_dn,
                search_filter=search_filter,
                attributes=["cn", "mail", "dn", "memberOf"],
            )

            if len(conn.entries) == 0:
                print(f"[LDAP] User not found: {username}")
                return None

            user_dn = conn.entries[0].entry_dn
            user_attrs = conn.entries[0]
            print(f"[LDAP] Found user: {user_dn}")
            conn.unbind()

            user_conn = Connection(
                self.server,
                user=user_dn,
                password=password,
                auto_bind=True,
            )
            print(f"[LDAP] Password verification: SUCCESS")
            user_conn.unbind()

            return {
                "dn": user_dn,
                "cn": str(user_attrs.cn),
                "email": str(user_attrs.mail) if user_attrs.mail else username,
                "groups": [str(g) for g in user_attrs.memberOf] if user_attrs.memberOf else [],
            }

        except core.exceptions.LDAPBindError as e:
            print(f"[LDAP] Authentication failed: {e}")
            return None
        except Exception as e:
            print(f"[LDAP] Error: {e}")
            return None

    def _get_service_account(self):
        return "CN=svc_ldap,OU=Service Accounts,DC=company,DC=com"

    def _get_service_password(self):
        return "service-account-password"

LDAP with Node.js (Active Directory)

const ActiveDirectory = require("activedirectory2");
const config = {
  url: "ldap://ad.company.com",
  baseDN: "DC=company,DC=com",
  username: "svc_ldap@company.com",
  password: "service-account-password",
};

const ad = new ActiveDirectory(config);

function authenticateUser(username, password) {
  return new Promise((resolve, reject) => {
    ad.authenticate(username, password, (err, auth) => {
      if (err) {
        console.log(`[LDAP] Auth error: ${err.message}`);
        return reject(err);
      }
      if (!auth) {
        console.log(`[LDAP] Auth failed: ${username}`);
        return resolve(null);
      }

      console.log(`[LDAP] Authenticated: ${username}`);

      ad.findUser(username, (err, user) => {
        if (err) {
          return resolve({ authenticated: true });
        }
        resolve({
          authenticated: true,
          dn: user.dn,
          cn: user.cn[0],
          email: user.mail,
          groups: user.memberOf || [],
          enabled: !user.userAccountControl || !(user.userAccountControl & 2),
        });
      });
    });
  });
}

async function login(username, password) {
  const result = await authenticateUser(username, password);
  if (!result) {
    return { error: "Invalid credentials" };
  }
  if (result.enabled === false) {
    return { error: "Account is disabled" };
  }
  return { user: result };
}

LDAP Search and Attribute Mapping

class LDAPGroupChecker:
    def __init__(self, auth):
        self.auth = auth

    def get_user_groups(self, username):
        conn = Connection(
            self.auth.server,
            user=self.auth._get_service_account(),
            password=self.auth._get_service_password(),
            auto_bind=True,
        )

        conn.search(
            search_base=self.auth.base_dn,
            search_filter=f"(mail={username})",
            attributes=["memberOf"],
        )

        if conn.entries:
            return [str(g) for g in conn.entries[0].memberOf]
        return []

    def is_user_in_group(self, username, group_name):
        groups = self.get_user_groups(username)
        return any(group_name.lower() in g.lower() for g in groups)

    def list_users_in_group(self, group_dn):
        conn = Connection(
            self.auth.server,
            user=self.auth._get_service_account(),
            password=self.auth._get_service_password(),
            auto_bind=True,
        )

        conn.search(
            search_base=self.auth.base_dn,
            search_filter=f"(memberOf={group_dn})",
            attributes=["cn", "mail"],
        )

        users = []
        for entry in conn.entries:
            users.append({
                "cn": str(entry.cn),
                "mail": str(entry.mail) if entry.mail else "",
            })
        return users

Common Mistakes

  1. Not using LDAPS (LDAP over SSL) exposes credentials in transit on the network.
  2. Constructing LDAP search filters with string concatenation leads to LDAP injection attacks.
  3. Using the user's credentials for the initial search bind instead of a service account.
  4. Not handling account lockout or disabled account status properly.
  5. Assuming all directory servers have the same schema and attribute names.
  6. Failing to close LDAP connections after authentication leads to connection pool exhaustion.

Practice Questions

  1. What is the difference between LDAP bind and LDAP search?

Bind authenticates to the LDAP server (verifies credentials). Search queries the directory for user records. The two-step Process: first bind as service account to search, then bind as the user to verify their password.

  1. What is LDAP injection and how do you prevent it?

LDAP injection is similar to SQL Injection — an attacker manipulates search filter input to modify the query logic. Prevent by using parameterized search filters or escaping special characters (*, (), &, |, !) in user input.

  1. How do you map LDAP attributes to application roles?

First, query the user's memberOf attribute during authentication. Then, map groups to roles in your application using a configuration file: {"CN=Admins,OU=Groups,...": "admin", "CN=Users,...": "user"}.

  1. Challenge: Build a complete LDAP authentication system with service account bind, user search, password verification, group-to-role mapping, LDAP injection prevention, connection pooling, and TLS encryption.

FAQ

What is the difference between Active Directory and OpenLDAP?

Active Directory is Microsoft's directory service with LDAP, Kerberos, DNS, and policy management. OpenLDAP is an open-source LDAP implementation. Both support LDAP authentication but have different schema defaults and management tools.

Can I use LDAP without a directory server?

No. LDAP is a protocol that requires a directory server (Active Directory, OpenLDAP, 389 Directory Server). You cannot use LDAP against a regular database — the directory server implements the LDAP protocol.

How do I handle LDAP connection failures?

Implement connection pooling with automatic retry. Cache recent successful authentications as a fallback. Alert on LDAP server unavailability. Consider a local cache of user credentials for critical-path authentication.

What happens if the LDAP server is slow?

LDAP authentication adds latency to every login. Set appropriate timeouts (5-10 seconds). Implement Caching of successful authentications (session tokens). Monitor LDAP response times and alert on degradation.

Mini Project: LDAP Browser CLI

Build a CLI tool that connects to an LDAP server, allows browsing the directory tree, searching for users and groups, and testing authentication.

import sys
from ldap3 import Server, Connection, ALL

class LDAPBrowser:
    def __init__(self, host, base_dn, username, password, use_ssl=True):
        port = 636 if use_ssl else 389
        server = Server(host, port=port, use_ssl=use_ssl, get_info=ALL)
        self.conn = Connection(server, user=username, password=password, auto_bind=True)
        self.base_dn = base_dn

    def search(self, filter_str, attributes=None):
        attrs = attributes or ["cn", "mail", "dn"]
        self.conn.search(self.base_dn, filter_str, attributes=attrs)
        for entry in self.conn.entries:
            print(f"DN: {entry.entry_dn}")
            for attr in attrs:
                if attr != "dn" and entry[attr]:
                    print(f"  {attr}: {entry[attr]}")
            print()

    def tree(self, dn=None):
        dn = dn or self.base_dn
        self.conn.search(dn, "(objectClass=*)", attributes=["ou", "cn", "objectClass"])
        for entry in self.conn.entries:
            indent = "  " * (entry.entry_dn.count(",") - self.base_dn.count(","))
            name = entry.ou.value if entry.ou else entry.cn.value if entry.cn else entry.entry_dn
            print(f"{indent}{name}")
            if "organizationalUnit" in entry.objectClass or "container" in entry.objectClass:
                self.tree(entry.entry_dn)

    def test_auth(self, username, password):
        try:
            test_conn = Connection(
                Server(self.conn.server.host),
                user=username, password=password, auto_bind=True
            )
            test_conn.unbind()
            print("Auth: SUCCESS")
        except Exception as e:
            print(f"Auth: FAILED ({e})")

if __name__ == "__main__":
    browser = LDAPBrowser(
        sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
    )
    browser.search("(mail=*@company.com)")

What's Next

Compare SAML vs OAuth to understand when to use each protocol, then explore token storage for secure client-side token management.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro