Browse docs

Production security with ldapjs

A checklist for taking ldapjs code from a local prototype to a secure production deployment.

On this page

A few things that are fine to skip while prototyping against a local test directory become important before ldapjs code reaches production.

Always use LDAPS or STARTTLS

Never bind with a real password over plain ldap://. Use ldaps:// (see LDAPS and TLS), and confirm tlsOptions isn't disabling verification:

Danger

ts
// Never ship this:
const client = ldap.createClient({
  url: "ldaps://dc1.example.com:636",
  tlsOptions: { rejectUnauthorized: false },
});

rejectUnauthorized: false accepts any certificate, including one from an attacker performing a man-in-the-middle attack. It's sometimes used temporarily against a self-signed local test server — never in a deployed environment.

Instead, trust the specific CA certificate your directory uses:

ts
const client = ldap.createClient({
  url: "ldaps://dc1.example.com:636",
  tlsOptions: {
    ca: [fs.readFileSync(process.env.LDAP_CA_CERT_PATH!)],
  },
});
Never build filters or DNs by string concatenation

Escape every value that comes from outside your code before it goes into a filter or a DN — see LDAP injection. This applies to usernames, emails, and anything else a user can influence.

Keep bind credentials out of source control

Service account credentials belong in environment variables or a secrets manager, not hardcoded or committed:

ts
const client = ldap.createClient({ url: process.env.LDAP_URL! });
client.bind(process.env.LDAP_BIND_DN!, process.env.LDAP_BIND_PASSWORD!, callback);
Set timeouts

Without a timeout and connectTimeout, a request against an unreachable or overloaded directory server can hang indefinitely, tying up whatever handled the incoming request. Set both explicitly (see Connecting) and make sure your application has a fallback path for when LDAP is unavailable.

Handle connection-level errors

A long-lived client can emit error events unrelated to any specific request — an unhandled error event on an EventEmitter crashes the Node process. Always attach a listener, as shown in Getting started with ldapjs.

Least-privilege service accounts

The service account used for searching should have read-only access scoped to what it actually needs, not a domain administrator account. If that account's credentials ever leak, the blast radius should be limited to directory reads.

Checklist
  • [ ] Connections use ldaps:// or STARTTLS, never plain LDAP with real credentials.
  • [ ] rejectUnauthorized is not disabled, and the correct CA certificate is trusted.
  • [ ] Every filter value and DN built from user input is escaped.
  • [ ] Bind credentials come from environment variables or a secrets manager.
  • [ ] timeout and connectTimeout are set.
  • [ ] The client's error event is handled.
  • [ ] The service account has least-privilege, read-only access.
What's next

If you're specifically targeting Active Directory, continue to Active Directory for the deeper conceptual background behind the Active Directory integration page.