Node.js LDAP Authentication Fork
ldapauth-fork is a Node.js library for authenticating users against an LDAP server. It's a maintained fork of the original `node-ldapauth` package, primarily created to integrate newer versions of `ldapjs`, enable `tlsOptions` support, and address various community-reported issues. The package provides a robust API for user authentication, including support for group membership checks and configurable search filters. It ships with TypeScript type definitions since v4.0.0 and utilizes Bunyan for logging, aligning with `ldapjs`'s logging approach. The current stable version is 6.1.0, with a release cadence that addresses bug fixes, dependency updates, and new features, indicating active maintenance. Key differentiators include its explicit support for modern `ldapjs` versions, comprehensive configuration options for diverse LDAP setups, and improved error handling through `EventEmitter` inheritance.
Common errors
-
TypeError: auth.authenticate is not a function
cause Attempting to call `authenticate` on an uninitialized or incorrectly imported `LdapAuth` instance, often due to incorrect CommonJS `require` syntax.fixEnsure `LdapAuth` is correctly imported as the default export using `const LdapAuth = require('ldapauth-fork');` for CommonJS or `import LdapAuth from 'ldapauth-fork';` for ESM, then instantiate it with `new LdapAuth(options);`. -
Error: Connect Timeout (ldapauth-fork)
cause The LDAP server did not respond within the configured connection timeout period, indicating network issues, an incorrect LDAP URL, or a non-responsive server.fixVerify the `url` in your `LdapAuthOptions` is correct and reachable. Check network connectivity between your application and the LDAP server. The `connectTimeout` option in `ldapjs` (which can be passed via `ldapauth-fork` options) can be adjusted if the server is slow to respond, though excessive timeouts may mask underlying issues. -
LdapAuth: Error: [LDAP_PROTOCOL_ERROR] 00000000: LdapErr: DSID-0C090C5D, comment: In order to perform this operation a successful bind must be completed on the connection.
cause This typically means the `bindDN` and `bindCredentials` provided for the admin user (if configured) are incorrect or lack the necessary permissions to perform the `searchBase` lookup.fixDouble-check the `bindDN` and `bindCredentials` in your `LdapAuthOptions`. Ensure the user specified by `bindDN` has sufficient read permissions on the `searchBase` to find user entries. If no `bindDN` is provided, ensure your LDAP server allows anonymous binds for searches.
Warnings
- breaking The `includeRaw` option has been removed from `LdapAuth` configuration. This is due to its removal from the underlying `ldapjs` library in its v3.x upgrade.
- breaking Major version update of the underlying `ldapjs` library to v3.0.4. While `ldapauth-fork` attempts to abstract these changes, direct interaction with `ldapjs` options or behaviors might be affected.
- breaking The `LdapAuth` class now inherits from `EventEmitter`. This changes how errors are propagated, specifically re-emitting `ldaps` errors.
- breaking The tracing module was changed from an unspecified prior logger to [Bunyan](https://github.com/trentm/node-bunyan). The logger instance is now passed forward to `ldapjs`.
- gotcha The `searchBase` option, while optional in some contexts, is critical for defining where user searches begin. Providing an empty string or an invalid `searchBase` can lead to authentication failures.
Install
-
npm install ldapauth-fork -
yarn add ldapauth-fork -
pnpm add ldapauth-fork
Imports
- LdapAuth
const LdapAuth = require('ldapauth-fork').default; // Incorrect if the main export is a default export, often LdapAuth is the default. import { LdapAuth } from 'ldapauth-fork/dist/LdapAuth';import LdapAuth from 'ldapauth-fork'; // ESM import { LdapAuth } from 'ldapauth-fork'; // For explicit named import if package.json exports it as such or if a specific configuration requires it. - LdapAuthOptions
import { LdapAuthOptions } from 'ldapauth-fork'; // Using 'type' keyword for types is best practice.import type { LdapAuthOptions } from 'ldapauth-fork'; - CommonJS require
const { LdapAuth } = require('ldapauth-fork'); // Incorrect if LdapAuth is the default export. const auth = new require('ldapauth-fork')(options); // Less readable and not idiomatic.const LdapAuth = require('ldapauth-fork');
Quickstart
import LdapAuth, { LdapAuthOptions } from 'ldapauth-fork';
import type { User } from 'ldapjs';
const options: LdapAuthOptions = {
url: process.env.LDAP_URL ?? 'ldaps://localhost:636',
bindDN: process.env.LDAP_BIND_DN ?? 'cn=admin,dc=example,dc=org',
bindCredentials: process.env.LDAP_BIND_CREDENTIALS ?? 'adminsecret',
searchBase: process.env.LDAP_SEARCH_BASE ?? 'ou=users,dc=example,dc=org',
searchFilter: process.env.LDAP_SEARCH_FILTER ?? '(uid={{username}})',
log: console // Simple logger, use a Bunyan instance in production
};
async function authenticateUser(username: string, password: string): Promise<User | null> {
const auth = new LdapAuth(options);
auth.on('error', (err) => {
console.error(`LDAP Authentication Error: ${err.message}`);
});
try {
console.log(`Attempting to authenticate user: ${username}`);
const user = await new Promise<User | null>((resolve, reject) => {
auth.authenticate(username, password, (err, user) => {
if (err) {
console.error(`Authentication failed for ${username}: ${err.message}`);
return reject(err);
}
if (user) {
console.log(`User ${username} authenticated successfully.`);
resolve(user as User);
} else {
console.log(`Authentication failed: No user found for ${username}.`);
resolve(null);
}
});
});
return user;
} catch (error) {
console.error('An unexpected error occurred during authentication:', error);
return null;
} finally {
await new Promise<void>((resolve, reject) => {
auth.close((err) => {
if (err) return reject(err);
resolve();
});
});
console.log('LDAP connection closed.');
}
}
// Example usage:
authenticateUser('testuser', 'testpassword')
.then(user => {
if (user) {
console.log('Authenticated User Details:', user);
}
})
.catch(console.error);