Skip to content
Lucas Mauro

What LDAP is and when to use it

A well-established protocol that defines allows clients to peform a variety of operations on a directory server: user authentication, data storage and retrieval and more.

authentication 6 min read

LDAP is the Lightweight Directory Access Protocol. As a protocol, it defines a set of standards that allow clients to perform a variety of operations in a directory server, such as storing, searching and retrieving data, authenticating clients, and more.

The protocol is usually described through four models, which we touch briefly throughout this note:

  • Information: what the data looks like;
  • Naming: how data is identified;
  • Functional: what operations exist;
  • Security: who can do what.

We can store virtually anything. The LDAP information model is based on entries, which hold a collection of attributes and have unique names associated to them, known as the Distinguished Name (DN). Each entry attribute has a type and one or more values.

The entries are arranged in a hierarchical tree-like structure, known as the Directory Information Tree (DIT). Entries at the first nodes of the tree have the highest hierarchy. The diagram below is an example, with a hypothetical hierarchy of secret access management to different entities (applications and persons) across different global regions.

graph TD
    root(( ))
    root --> east1["reg=us-east-1"]
    root --> west1["reg=us-west-1"]
    root --> sa1["reg=sa-east-1"]
    west1 --> svc["svc=secrets-manager"]
    svc --> app["type=application"]
    svc --> people["type=people"]
    app --> uid["uid=nice-app"]

An entry is referenced by its Distinguished Name, much similarly to Unix directory paths: we take the name of the entry itself (its Relative Distinguished Name, or RDN) and concatenate the names of its ancestor entries. As an example, the last node on the tree above has the RDN of uid=nice-app and the DN of uid=nice-app,type=application,svc=secrets-manager,reg=us-west-1.

LDAP defines operations for reading from and writing to the directory, though it is more commonly used for searching data. The search operations can be done on nodes directly or on a hierarchy of nodes.

For example, we may want to search the entire directory subtree precisely at and below svc=secrets-manager for an app with uid=nice-app and retrieve any information regarding this matching node or we may want to retrieve the entire svc=secrets-manager subtree; both are possible.

LDAP supports three forms of authentication:

  • Anonymous: no identity;
  • Simple: credentials-based, with Distinguished Name + password;
  • Simple Authentication and Security Layer (SASL): a framework where the client names a mechanism (e.g. Kerberos, TLS certificates) and LDAP transports the authentication details as opaque bytes to the directory server (LDAP itself is not aware of any detail of the selected mechanism).

There is, however, no definition of authorisation in LDAP itself, so it is up to each directory server to enforce it as it best sees fit.

The object class determines what attributes an entry either must or may have, and an entry may have multiple object classes associated to it. As an example, let’s consider a person object class that must have name and ssn and may have phoneNumber, and another object class called employee that requires name and title. The entry will have required name, ssn and title attributes (enforced by the combination of the two object classes) and an optional phoneNumber attribute.

A single logical directory can be spread across multiple servers, and the actual data can be spread between them either in replication, partitions or a mix of both.

With replication, the client has a consistent view of the entries regardless of which server it queries. The caveat here is that the replication is done asynchronously, so for a period of time the data may be stale.

With partitioning, a server that receives a request for an entry that it doesn’t contain can respond with a referral. This is a pointer that tells the client which other server to ask instead.

  sequenceDiagram
      participant C as Client
      participant S1 as LDAP Server 1
      participant S2 as LDAP Server 2

      C->>S1: question (e.g. search)
      Note over S1: Requested entry stored elsewhere
      S1-->>C: referral to Server 2
      C->>S2: same question
      S2-->>C: answer

An LDAP directory server may be a good fit when our workflow is heavy on reads and light on writes. This is usually the case for applications that handle centralised identities for user accounts or hierarchical structures such as organisational charts.

This may not be the right tool, however, when failing to update entries is damaging. Since the protocol does not support transactions, if a single update request touches multiple entries and the operation fails halfway through, LDAP gives us no mechanism to undo what was written before the failure occurred. It’s true that a directory server may try to work around this, but the protocol itself does not cover the concept of transactions or anything similar.

Another aspect to consider is that replication across servers is done asynchronously, meaning that the data will be eventually consistent; we may read stale data from a server that is yet to be updated.

In other words, LDAP trades write throughput and safety for reading speed.

User information is stored no differently than any other information: in entries. Commonly, an entry of this kind will have attributes such as userPassword, mail and cn (common name). The difference is that, in order to prevent clients from obtaining user passwords, the servers should restrict operations based on an access control list (ACL). It is also highly recommended that passwords are stored as a salted hash.

Given that the client cannot compare the credentials itself and that it usually only knows the username (not the actual Distinguished Name), user authentication takes two steps.

Search: an application binds itself to the directory server using a service account (or anonymously when allowed) and searches for an entry that matches the identity to be authenticated. If not found, then we stop here.

Bind: when the user entry is found, the application sends another bind request providing the password that the user typed. The directory server hashes the input the same way it does for the stored password and compares them both internally. If there is a match, the binding is accepted.

The binding is done via TCP and the server keeps track of which connection is bound to which identity. This means that any new request done by the client on that connection is evaluated on behalf of the bound identity. In the event of a connection disruption, the binding is also lost.

  1. Network locality: since LDAP has no notion of federated identity across organisations or networks, it assumes that the client can access the directory server directly over a TCP connection. In practice, LDAP is limited to whatever is within its own data centre. This becomes a problem when we want to have applications on different networks to authenticate against our directory (this gap is solved by SAML and by OIDC);
  2. No ACID: the protocol has no concept of transactions, so if write operations must be safe across all requests, then this is a hard limitation;
  3. Credential exposure to applications: the user provides the plain-text password to the application, which forwards it to the directory server on their behalf. Even though the connection may be encrypted, it is still both a burden and a vulnerability for the application to obtain raw credentials.

Comments