activedirectory vs ldapjs
Implementing LDAP Authentication in Node.js
activedirectoryldapjs

Implementing LDAP Authentication in Node.js

Both activedirectory and ldapjs are Node.js packages used to interact with LDAP servers, such as Microsoft Active Directory. activedirectory is a higher-level wrapper designed specifically for Active Directory environments, simplifying common tasks like user authentication and group lookup. ldapjs is a lower-level, protocol-focused client that implements the LDAP v3 specification directly, offering granular control over connections, searches, and modifications. While activedirectory aims for ease of use, ldapjs provides the foundation many other libraries build upon.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
activedirectory0537-10611 years agoMIT
ldapjs01,622334 kB323 years agoMIT

activedirectory vs ldapjs: LDAP Integration Patterns

Both activedirectory and ldapjs enable Node.js applications to communicate with directory services like Microsoft Active Directory. They solve the same core problem — verifying user credentials and fetching directory data — but they approach it with different levels of abstraction and maintenance status. Let's compare how they handle real-world engineering tasks.

🛠️ Maintenance and Long-Term Support

activedirectory is considered legacy software.

  • The package has not seen significant updates in many years.
  • It relies on older Node.js patterns and may not work well with modern runtimes.
// activedirectory: Legacy callback patterns
const ActiveDirectory = require('activedirectory');
const ad = new ActiveDirectory(config);
// Risk: No recent security patches or Node.js version updates

ldapjs is actively maintained.

  • It receives regular updates to support new Node.js versions.
  • The community maintains it as the standard LDAP client for JavaScript.
// ldapjs: Modern maintenance
const ldap = require('ldapjs');
const client = ldap.createClient({ url: 'ldap://localhost' });
// Benefit: Active security patches and community support

🔌 Connection Setup and Configuration

activedirectory wraps configuration in a simple object.

  • You pass domain details directly to the constructor.
  • It handles connection pooling internally with less visibility.
// activedirectory: Simplified config
const config = {
  url: 'ldaps://dc.example.com',
  baseDN: 'dc=example,dc=com',
  username: 'admin@example.com',
  password: 'secret'
};
const ad = new ActiveDirectory(config);

ldapjs requires explicit client creation.

  • You define the client and manage binding separately.
  • This offers more control over TLS and connection lifecycle.
// ldapjs: Explicit client setup
const client = ldap.createClient({
  url: 'ldaps://dc.example.com',
  tlsOptions: { rejectUnauthorized: true }
});
client.bind('admin@example.com', 'secret', (err) => { /*...*/ });

🔐 User Authentication Flow

activedirectory provides a dedicated authenticate method.

  • It abstracts the bind operation into a single function call.
  • Useful for quick login checks but hides protocol details.
// activedirectory: One-step auth
ad.authenticate(username, password, (err, auth) => {
  if (err) return console.error(err);
  if (auth) console.log('Login successful');
});

ldapjs uses the standard LDAP bind operation.

  • You must construct the user DN or search first.
  • Gives you full control over how credentials are verified.
// ldapjs: Manual bind flow
const dn = `cn=${username},ou=users,dc=example,dc=com`;
client.bind(dn, password, (err) => {
  if (!err) console.log('Login successful');
  client.unbind();
});

🔍 Searching Directory Data

activedirectory uses helper methods for common searches.

  • Methods like findUser simplify looking up specific entries.
  • Less flexible if you need complex LDAP filters.
// activedirectory: Helper methods
ad.findUser(username, (err, user) => {
  if (err) return console.error(err);
  console.log(user.mail);
});

ldapjs uses generic search with filters.

  • You write standard LDAP filter strings.
  • Supports complex queries across any directory structure.
// ldapjs: Generic search
const opts = {
  filter: `(sAMAccountName=${username})`,
  scope: 'sub'
};
client.search('dc=example,dc=com', opts, (err, res) => {
  res.on('searchEntry', (entry) => console.log(entry.object));
});

🧩 Error Handling and Control

activedirectory wraps errors in a custom format.

  • Errors are passed to callbacks but may lack protocol details.
  • Harder to debug specific LDAP result codes.
// activedirectory: Wrapped errors
ad.authenticate(user, pass, (err, auth) => {
  if (err) {
    // Generic error object
    console.error(err.message);
  }
});

ldapjs exposes raw LDAP errors.

  • You get specific result codes (e.g., Invalid Credentials).
  • Better for building robust retry logic or detailed logs.
// ldapjs: Protocol errors
client.bind(dn, pass, (err) => {
  if (err) {
    // Specific LDAP error codes
    console.error(err.code, err.message);
  }
});

📝 TypeScript and Modern Development

activedirectory lacks official type definitions.

  • You must write your own .d.ts files or use any.
  • Increases risk of runtime errors in large codebases.
// activedirectory: No types
// @ts-ignore
const ad = new ActiveDirectory(config);
// Developer must guess method signatures

ldapjs has community-driven type support.

  • Packages like @types/ldapjs exist.
  • Better integration with modern TypeScript projects.
// ldapjs: Type support available
import * as ldap from 'ldapjs';
const client: ldap.Client = ldap.createClient({ url: '...' });
// Editor autocomplete and type checking work

🤝 Similarities: Shared Ground Between Packages

While the implementation differs, both packages share core LDAP capabilities.

1. 🌐 Protocol Support

  • Both support LDAP v3 and LDAPS (secure LDAP).
  • Can connect to Microsoft AD, OpenLDAP, and others.
// Both support secure connections
// activedirectory
const ad = new ActiveDirectory({ url: 'ldaps://...' });

// ldapjs
const client = ldap.createClient({ url: 'ldaps://...' });

2. 👥 User Lookup

  • Both can retrieve user attributes like email or groups.
  • Essential for building login profiles.
// activedirectory
ad.findUser('john', (err, user) => { /*...*/ });

// ldapjs
client.search('dc=...', { filter: '(cn=john)' }, (err, res) => { /*...*/ });

3. ⚙️ Node.js Integration

  • Both run on Node.js server environments.
  • Commonly used in Express APIs or authentication middleware.
// Express middleware example (conceptual)
app.post('/login', (req, res) => {
  // Use either package here to verify req.body.password
});

📊 Summary: Key Differences

Featureactivedirectoryldapjs
Maintenance⚠️ Legacy / Inactive✅ Active / Standard
Abstraction🧩 High (AD specific)🔧 Low (Protocol focused)
Auth Method🔑 .authenticate()🔑 .bind()
Search🔍 Helper methods🔍 Raw filters
TypeScript❌ Poor support✅ Community types available
Use Case🕰️ Legacy maintenance🚀 New development

💡 The Big Picture

activedirectory is like an old utility knife 🔪 — it works for specific tasks you've always done, but the blade is dulling. It simplifies Active Directory interactions but carries the risk of unmaintained code. Use it only if you are stuck maintaining an older system.

ldapjs is like a precision tool set 🧰 — it requires more skill to use but handles any job you throw at it. It is the industry standard for LDAP in Node.js and ensures your authentication layer remains secure and up-to-date.

Final Thought: For any new project, ldapjs is the clear choice. Security and maintenance matter more than minor convenience gains. If you need TypeScript support, also consider ldapts, a modern fork of ldapjs built with types in mind.

How to Choose: activedirectory vs ldapjs

  • activedirectory:

    Choose activedirectory only if you are maintaining a legacy system that already depends on it and requires minimal changes. It simplifies basic Active Directory tasks but has not seen significant updates in years. Avoid using it for new projects due to potential security risks and lack of modern feature support.

  • ldapjs:

    Choose ldapjs for any new development requiring LDAP integration. It is actively maintained, supports modern Node.js versions, and gives you full control over the LDAP protocol. It is the better choice for complex queries, custom authentication flows, and long-term project stability.

README for activedirectory

ActiveDirectory for Node

ActiveDirectory is an ldapjs client for authN (authentication) and authZ (authorization) for Microsoft Active Directory with range retrieval support for large Active Directory installations. This code was a port of an existing C# library (not published) that I had written a few years ago. Here are the key features

  • Authenticate
  • Authorization (via group membership information)
  • Nested groups support
  • Range specifier / retrieval support (http://msdn.microsoft.com/en-us/library/dd358433.aspx)
  • Automatic paging support (Active Directory results (MaxPageSize) limited to 1000 per request by default)
  • Recycle bin (tombstone) query support
  • Referral support

Required Libraries

ActiveDirectory uses the following additional node modules:

  • underscore - a utility-belt library for JavaScript that provides a lot of the functional programming support
  • async - Async utilities for node and the browser
  • ldapjs - A pure JavaScript, from-scratch framework for implementing LDAP clients and servers in Node.js
  • bunyan - A simple and fast JSON logging module for node.js services

Installation

npm install activedirectory

Usage

var ActiveDirectory = require('activedirectory');
var config = { url: 'ldap://dc.domain.com',
               baseDN: 'dc=domain,dc=com',
               username: 'username@domain.com',
               password: 'password' }
var ad = new ActiveDirectory(config);

The username and password specified in the configuration are what are used for user and group lookup operations.

Documentation


### authenticate(username, password, callback)

Authenticates the username and password by doing a simple bind with the specified credentials.

Arguments

  • username - The username to authenticate.
  • password - The password to use for authentication.
  • callback(err, authenticated) - A callback which is called after authentication is completed.

Example

var ad = new ActiveDirectory(config);
var username = 'john.smith@domain.com';
var password = 'password';

ad.authenticate(username, password, function(err, auth) {
  if (err) {
    console.log('ERROR: '+JSON.stringify(err));
    return;
  }
  
  if (auth) {
    console.log('Authenticated!');
  }
  else {
    console.log('Authentication failed!');
  }
});

### isUserMemberOf(opts, username, groupName, callback)

Checks to see if a user is a member of the specified group. This function will also check for group membership inside of a group. Even if a user is not explicity listed as a member of a particular group, if a group that the user is a member of belongs to the group, then this function will return true.

Arguments

Example

var username = 'user@domain.com';
var groupName = 'Employees';

var ad = new ActiveDirectory(config);
var ad.isUserMemberOf(username, groupName, function(err, isMember) {
  if (err) {
    console.log('ERROR: ' +JSON.stringify(err));
    return;
  }

  console.log(username + ' isMemberOf ' + groupName + ': ' + isMember);
});

### groupExists(opts, groupName, callback)

Checks to see if the specified group exists.

Arguments

Example

var groupName = 'Employees';

var ad = new ActiveDirectory(config);
ad.groupExists(groupName, function(err, exists) {
  if (err) {
    console.log('ERROR: ' +JSON.stringify(err));
    return;
  }

  console.log(groupName + ' exists: ' + exists);
});

### userExists(opts, username, callback)

Checks to see if the specified user exists.

Arguments

Example

var username = 'john.smith@domain.com';

var ad = new ActiveDirectory(config);
ad.userExists(username, function(err, exists) {
  if (err) {
    console.log('ERROR: ' +JSON.stringify(err));
    return;
  }

  console.log(username + ' exists: ' + exists);
});

### getUsersForGroup(opts, groupName, callback)

For the specified group, retrieve all of the users that belong to the group. If the group contains groups, then the members of those groups are recursively retrieved as well to build a complete list of users that belong to the specified group.

Arguments

Example

var groupName = 'Employees';

var ad = new ActiveDirectory(config);
ad.getUsersForGroup(groupName, function(err, users) {
  if (err) {
    console.log('ERROR: ' +JSON.stringify(err));
    return;
  }

  if (! users) console.log('Group: ' + groupName + ' not found.');
  else {
    console.log(JSON.stringify(users));
  }
});

### getGroupMembershipForUser(opts, username, callback)

For the specified username, retrieve all of the groups that a user belongs to. If a retrieved group is a member of another group, then that group is recursively retrieved as well to build a complete hierarchy of groups that a user belongs to.

Arguments

Example

var sAMAccountName = 'john.smith@domain.com';

var ad = new ActiveDirectory(config);
ad.getGroupMembershipForUser(sAMAccountName, function(err, groups) {
  if (err) {
    console.log('ERROR: ' +JSON.stringify(err));
    return;
  }

  if (! groups) console.log('User: ' + sAMAccountName + ' not found.');
  else console.log(JSON.stringify(groups));
});

### getGroupMembershipForGroup(opts, groupName, callback)

For the specified group, retrieve all of the groups that the group is a member of. If a retrieved group is a member of another group, then that group is recursively retrieved as well to build a complete hierarchy of groups that a user belongs to.

Arguments

Example

var groupName = 'Employees';

var ad = new ActiveDirectory(config);
ad.getGroupMembershipForGroup(groupName, function(err, groups) {
  if (err) {
    console.log('ERROR: ' +JSON.stringify(err));
    return;
  }

  if (! groups) console.log('Group: ' + groupName + ' not found.');
  else console.log(JSON.stringify(groups));
});

### find(opts, callback)

Perform a generic search for the specified LDAP query filter. This function will return both groups and users that match the specified filter. Any results not recognized as a user or group (i.e. computer accounts, etc.) can be found in the 'other' attribute / array of the result.

Arguments

Example

var _ = require('underscore');
var query = 'cn=*Exchange*';
var opts = {
  includeMembership : [ 'group', 'user' ], // Optionally can use 'all'
  includeDeleted : false
};

var ad = new ActiveDirectory(config);
ad.find(query, function(err, results) {
  if ((err) || (! results)) {
    console.log('ERROR: ' + JSON.stringify(err));
    return;
  }

  console.log('Groups');
  _.each(results.groups, function(group) {
    console.log('  ' + group.cn);
  });

  console.log('Users');
  _.each(results.users, function(user) {
    console.log('  ' + user.cn);
  });

  console.log('Other');
  _.each(results.other, function(other) {
    console.log('  ' + other.cn);
  });
});

### findDeletedObjects(opts, callback)

If tombstoning (recycle bin) is enabled for the Active Directory installation, use findDeletedObjects to retrieve items in the recycle bin.

More information about tombstoning and enabling can be found at:

Note: That when an LDAP entry / object is tombstoned, not all attributes for that item are retained. This is a limitation of Active Directory itself and not the library itself.

Arguments

  • opts - Optional parameters to extend or override functionality. See optional parameters. If only a string is provided, then the string is assumed to be an LDAP filter
  • callback - The callback to execute when completed. callback(err: {Object}, result: {Array})

If the baseDN is not specified, then a RootDSE query will be performed on the attached URL and 'ou-Deleted Objects' will be appended.

Example

var url = 'ldap://yourdomain.com';
var opts = {
  baseDN: 'ou=Deleted Objects, dc=yourdomain, dc=com',
  filter: 'cn=*Bob*'
};
ad.findDeletedObjects(opts, function(err, result) {
  if (err) {
    console.log('ERROR: ' +JSON.stringify(err));
    return;
  }

  console.log('findDeletedObjects: '+JSON.stringify(result));
});

### findUser(opts, username, callback)

Looks up or finds a username by their sAMAccountName, userPrincipalName, distinguishedName (dn) or custom filter. If found, the returned object contains all of the requested attributes. By default, the following attributes are returned:

  • userPrincipalName, sAMAccountName, mail, lockoutTime, whenCreated, pwdLastSet, userAccountControl, employeeID, sn, givenName, initials, cn, displayName, comment, description

Arguments

Example

// Any of the following username types can be searched on
var sAMAccountName = 'username';
var userPrincipalName = 'username@domain.com';
var dn = 'CN=Smith\\, John,OU=Users,DC=domain,DC=com';

// Find user by a sAMAccountName
var ad = new ActiveDirectory(config);
ad.findUser(sAMAccountName, function(err, user) {
  if (err) {
    console.log('ERROR: ' +JSON.stringify(err));
    return;
  }

  if (! user) console.log('User: ' + sAMAccountName + ' not found.');
  else console.log(JSON.stringify(user));
});

### findUsers(opts, callback)

Perform a generic search for users that match the specified filter. The default LDAP filter for users is specified as (&(|(objectClass=user)(objectClass=person))(!(objectClass=computer))(!(objectClass=group)))

Arguments

Example

var query = 'cn=*George*';

var ad = new ActiveDirectory(config);
ad.findUsers(query, true, function(err, users) {
  if (err) {
    console.log('ERROR: ' +JSON.stringify(err));
    return;
  }

  if ((! users) || (users.length == 0)) console.log('No users found.');
  else {
    console.log('findUsers: '+JSON.stringify(users));
  }
});

### findGroup(opts, groupName, callback)

Looks up or find a group by common name (CN) which is required to be unique in Active Directory or optionally by the distinguished name. Supports groups with range retrieval specifiers. The following attributes are returned by default for the group:

  • objectCategory, distinguishedName, cn, description, member

Arguments

Example

// Any of the following group names can be searched on
var groupName = 'Employees';
var dn = 'CN=Employees,OU=Groups,DC=domain,DC=com'

// Find group by common name
var ad = new ActiveDirectory(config);
ad.findGroup(groupName, function(err, group) {
  if (err) {
    console.log('ERROR: ' +JSON.stringify(err));
    return;
  }

  if (! user) console.log('Group: ' + groupName + ' not found.');
  else {
    console.log(group);
    console.log('Members: ' + (group.member || []).length);
  }
});

### findGroups(opts, callback)

Perform a generic search for groups that match the specified filter. The default LDAP filter for groups is specified as (&(objectClass=group)(!(objectClass=computer))(!(objectClass=user))(!(objectClass=person)))

Arguments

Example

var query = 'CN=*Admin*';

var ad = new ActiveDirectory(config);
ad.findGroups(query, function(err, groups) {
  if (err) {
    console.log('ERROR: ' +JSON.stringify(err));
    return;
  }

  if ((! groups) || (groups.length == 0)) console.log('No groups found.');
  else {
    console.log('findGroups: '+JSON.stringify(groups));
  }
});

### getRootDSE(url, attributes, callback)

Retrieves the root DSE for the specified url. Can be called statically.

Arguments

  • url - The url to retrieve the root DSE for.
  • attributes - The optional list of attributes to retrieve. Returns all if not specified.
  • callback - The callback to execute when completed. callback(err: {Object}, result: {Object})

Example

var url = 'ldap://yourdomain.com';
ActiveDirectory.prototype.getRootDSE(url, [ 'defaultNamingContext' ], function(err, result) {
  if (err) {
    console.log('ERROR: ' +JSON.stringify(err));
    return;
  }

  console.log('getRootDSE: '+JSON.stringify(result));
});

// Or can be called with an instance...
var ad = new ActiveDirectory(config);
ad.getRootDSE(function(err, result) {
  //...
});

Advanced Usage

Attributes

By default, the following attributes are returned for users and groups:

  • user - distinguishedName, userPrincipalName, sAMAccountName, mail, lockoutTime, whenCreated, pwdLastSet, userAccountControl, employeeID, sn, givenName, initials, cn, displayName, comment, description
  • group - distinguishedName, objectCategory, cn, description

If you need to override those defaults, then you can override them when you create your ActiveDirectory instance:

var ad = new ActiveDirectory({ url: 'ldap://dc.domain.com',
                               baseDN: 'dc=domain,dc=com',
                               username: 'username@domain.com',
                               password: 'password',
                               attributes: {
                                 user: [ 'myCustomAttribute', 'mail', 'userPrinicipalName' ],
                                 group: [ 'anotherCustomAttribute', 'objectCategory' ]
                               }
                              });

If overriding the 'user' or 'group' attribute, you must specify ALL of the attributes you want. The existing defaults will be overridden. Optionally, you can override the attributes on a per call basis using the 'opts' parameter.

Referrals

By default, referral chasing is disabled. To enable it, specify a referrals attribute when you create your instance. The referrals object has the following syntax:

{
  referrals: {
    enabled: false,
    excluded: [
      'ldaps?://ForestDnsZones\./.*',
      'ldaps?://DomainDnsZones\./.*',
      'ldaps?://.*/CN=Configuration,.*'
    ]
  }
}

The 'excluded' options is a list of regular expression filters to ignore specific referrals. The default exclusion list is included above, ignoring the special partitions that ActiveDirectory creates by default. To specify these options, override them as follows:

var ad = new ActiveDirectory({ url: 'ldap://dc.domain.com',
                               baseDN: 'dc=domain,dc=com',
                               username: 'username@domain.com',
                               password: 'password',
                               attributes: { ... },
                               referrals: {
                                 enabled: true,
                                 excluded: [ ]
                               }
                              });

If you enable referral chasing, the specified username MUST be a userPrincipalName.

Custom Entry Parsing

if you want to manipulate the search entry in a different way or perhaps augment the search result with additional data, you can pass a custom parser. This is useful, for example, in case you want to change the objectSid or GUID which are binary values.

Example:

function customEntryParser(entry, raw, callback){
    if (raw.hasOwnProperty("objectSid")){
        entry.objectSid = raw.objectSid;
    }
    if (raw.hasOwnProperty("objectGUID")){
        entry.objectGUID = raw.objectGUID;
    }
    callback(entry);
};

If you want to specify your own parser you can override the default parser as follows:

var ad = new ActiveDirectory({ url: 'ldap://dc.domain.com',
                               baseDN: 'dc=domain,dc=com',
                               username: 'username@domain.com',
                               password: 'password',
                               attributes: { ... },
                               referrals: { ... },
                               entryParser : customEntryParser
                              });

Optionally, you can specify your custom entry parser as part of the 'opts' object. See optional parameters for more information.

var opts = function(entry, raw, callback) {
  entry.retrievedAt = new Date();
  callback(entry);
};
ad.findUser(opts, 'userPrincipalName=bob@domain.com', function(err, user) {
 ...
});
### Optional Parameters / Extended Functionality

Any method which takes an 'opts' parameter allows for additional options. Options for both activedirectory.js and the internal ldapjs client are supported.

Currently supported ldapjs opts are:

  • url - a valid LDAP url.
  • host - the host name to connect to (used with port in lieu of url)
  • port - the port to connect to (used with hostname in lieu of url)
  • secure - indicates if ldaps:// vs ldap:// is used. (used with hostname/port in lieu of url)
  • tlsOptions - additrional tls options (see ldapjs for more information)
  • socketPath - If you're running an LDAP server over a Unix Domain Socket, use this.
  • log - You can optionally pass in a bunyan instance the client will use to acquire a logger. The client logs all messages at the trace level.
  • timeout - How long the client should let operations live for before timing out. Default is Infinity.
  • idleTimeout - How long the client should wait before timing out on TCP connections. Default is up to the OS.
  • bindDN - The DN all connections should be bound as.
  • bindCredentials - The credentials to use with bindDN.
  • scope - One of base, one, or sub. Defaults to base.
  • filter - A string version of an LDAP filter (see below), or a programatically constructed Filter object. Defaults to (objectclass=*).
  • attributes - attributes to select and return (if these are set, the server will return only these attributes). Defaults to the empty set, which means all attributes.
  • sizeLimit - the maximum number of entries to return. Defaults to 0 (unlimited).
  • timeLimit - the maximum amount of time the server should take in responding, in seconds. Defaults to 10. Lots of servers will ignore this.

Options for activedirectory.js:

Example

var opts = {
  scope: 'sub',
  filter: 'objectClass=User',
  includeMembership: [ 'user' ],
  entryParser: function(entry, raw, callback) {
    // returning null with exclude result
    if (entry.ignore) return(null);

    entry.retrievedAt = new Date();
    entry.preferredServer = getPreferredServerFromDatabase(entry.userPrincipalName);

    callback(entry);  
  }
};