Browse docs

Groups with ldapjs

Reading a user's group membership and checking membership in a specific group with ldapjs.

On this page

This page implements the two group-membership patterns described conceptually in Users and groups: listing a user's groups, and checking membership in one specific group.

Listing a user's groups via memberOf

If the directory maintains a reverse memberOf attribute (Active Directory does natively; OpenLDAP needs the memberof overlay), this is a single search:

ts
async function getUserGroups(client: ldap.Client, userDn: string): Promise<string[]> {
  return new Promise((resolve, reject) => {
    const opts: ldap.SearchOptions = {
      filter: "(objectClass=*)",
      scope: "base",
      attributes: ["memberOf"],
    };

    client.search(userDn, opts, (err, res) => {
      if (err) return reject(err);

      let groups: string[] = [];
      res.on("searchEntry", (entry) => {
        const value = entry.pojo.attributes.find((a) => a.type === "memberOf")?.values;
        groups = value ?? [];
      });
      res.on("error", reject);
      res.on("end", () => resolve(groups));
    });
  });
}

The result is an array of group DNs, not group names — if you need display names, resolve each DN with a follow-up lookup, or search groups directly (below) and read their cn.

Checking membership without memberOf

If memberOf isn't available, search the group instead, filtering on member:

ts
async function isMember(
  client: ldap.Client,
  groupDn: string,
  userDn: string,
): Promise<boolean> {
  return new Promise((resolve, reject) => {
    const opts: ldap.SearchOptions = {
      filter: `(member=${escapeFilterValue(userDn)})`,
      scope: "base",
      attributes: ["dn"],
    };

    client.search(groupDn, opts, (err, res) => {
      if (err) return reject(err);

      let found = false;
      res.on("searchEntry", () => (found = true));
      res.on("error", reject);
      res.on("end", () => resolve(found));
    });
  });
}

Because a DN can contain characters like commas and +, always escape it the same way you would any other filter value — see LDAP filters.

Note

Both patterns assume you already have the user's or group's DN. If you're starting from just a username, combine this with the search step from Authentication with ldapjs.

Nested groups

Active Directory resolves nested group membership into memberOf automatically (up to its recursion limit), so a direct memberOf read already reflects indirect membership. Directories without that feature require walking group membership recursively yourself, which is significantly more expensive and usually only necessary for authorization systems with deep role hierarchies.

What's next

If your target directory is Active Directory specifically, continue to Active Directory integration in Node.js for AD-specific attributes and quirks.