ldap-native
v0.1.4
Published
Native Node.js LDAP client for OpenLDAP and Active Directory, ldapts-compatible with SASL/Kerberos/GSSAPI support
Downloads
95
Maintainers
Readme
ldap-native
Native Node.js LDAP client for OpenLDAP and Active Directory environments, with an API designed to stay close to ldapts. ldap-native replaces the JavaScript socket/BER transport with a Node-API addon backed by OpenLDAP libldap, while keeping the familiar Client-centric developer experience and adding SASL / Kerberos / GSSAPI entry points.
This package is aimed at infrastructure and platform teams that need a migration-friendly LDAP client for Node.js services: internal authentication, enterprise IAM, directory lookups, account sync jobs, SSO-adjacent integrations, and other server-side directory workflows where native LDAP support matters.
Why ldap-native
- Node.js LDAP client with native OpenLDAP bindings instead of a pure JavaScript transport layer
ldapts-compatible API shape for lower-friction migration from existing TypeScript or JavaScript codebases- SASL, Kerberos, and GSSAPI bind support for enterprise directory environments
- CommonJS, ESM, and TypeScript declarations for modern Node package consumption
- OpenLDAP / Active Directory migration focus rather than low-level protocol experimentation
- Cross-platform packaging path with source builds today and staged prebuild publication support
Install
npm install ldap-nativeIf a matching prebuild is available for your platform, install uses it. Otherwise the package falls back to a local native build against the system LDAP client libraries.
Ideal use cases
- Replace
ldaptswhile keeping nearly the same publicClientAPI - Standardize Node.js LDAP access across internal services
- Talk to OpenLDAP, Active Directory, or Kerberos-enabled directory infrastructure
- Build authentication, provisioning, or directory synchronization services in Node.js
- Adopt a native LDAP backend without rewriting application-level LDAP flows
At a glance
ldap-native combines three concerns in one package:
- compatibility-oriented package exports for
ldaptsconsumers - a Node-API addon that calls OpenLDAP directly
- CI/release automation for cross-platform source builds and staged prebuild distribution
Feature goals
- keep the familiar
ldaptsClientAPI shape - replace the TypeScript socket/BER transport with OpenLDAP
libldap - add
bind('GSSAPI')support for Kerberos-enabled environments - preserve common helper exports for controls, filters, BER, DN, and postal address helpers
- validate migration-relevant behavior against upstream
ldaptstests - prepare the package for multi-platform prebuild publication
Package positioning
| Topic | ldap-native position |
| --- | --- |
| Node.js LDAP client | Native addon backed by OpenLDAP libldap |
| ldapts migration | Public Client API intentionally stays close to upstream |
| ldapjs migration | Promise-based destination with explicit migration guidance |
| Enterprise auth | SASL / Kerberos / GSSAPI entry points included |
| Distribution model | CommonJS + ESM + generated .d.ts output |
| Infrastructure fit | Designed for server-side directory access, IAM, auth, and sync workloads |
Current status
Implemented now:
new Client(options)bind(dn, password)for simple bindbind('PLAIN' | 'EXTERNAL' | 'GSSAPI')startTLS(options)search()searchPaginated()compare()add()modify()del()modifyDN()exop()including LDAP Who Am I (1.3.6.1.4.1.4203.1.11.3)- root exports for controls, filters, BER helpers, DN helpers, and postal address helpers
- CommonJS and ESM import surface covered by import tests
- a native loader that resolves local builds and staged
prebuilds/ - a GitHub Actions matrix for API compatibility on Linux/macOS/Windows plus native build jobs for Linux/macOS and MSVC-based Windows builds
Compatibility evidence now includes:
- 184 upstream
ldaptstests executed directly (Controls,FilterParser,PostalAddress, BER, DN, and filter suites) - 13 client parity tests covering the migration-relevant public scenarios from upstream
Client.test.ts - package unit tests, import tests, and optional real OpenLDAP integration tests
See docs/COMPATIBILITY.md for the exact boundary between direct upstream execution and client parity coverage.
Migration paths
- Migrating from
ldapts: low-friction package replacement in the common case - Migrating from
ldapjs: callback/event-driven code usually needs explicitasync/awaitand result-shape updates
Start with:
Important limits and non-goals
This repository targets migration-friendly compatibility with the public ldapts API, not a byte-for-byte recreation of the original internal transport layer.
Current boundaries to keep in mind:
- generic
Controlto nativeLDAPControlencoding is still partial - some parity work intentionally excludes private socket/BER pipeline assertions from upstream
Client.test.ts - the current native addon uses synchronous
libldapcalls behind Promise-based JavaScript methods; production hardening should move blocking LDAP work into async workers
Native install details
ldap-native does not need an OpenLDAP server process on the local machine. Linux and macOS source builds rely on OpenLDAP client libraries (libldap, liblber) and Cyrus SASL; Windows source builds use the system Wldap32 SDK via MSVC.
The npm install flow now behaves as follows:
- Try a packaged prebuild for the current platform.
- If no compatible prebuild is present, fall back to a local source build.
- If native dependencies are missing, print platform-specific install commands.
The package never runs apt, dnf, brew, or apk for you.
Common system dependencies
Install the native prerequisites before npm install if you expect to build from source, or if a packaged prebuild fails to load because runtime libraries are missing.
| Platform | Typical packages |
| --- | --- |
| Debian / Ubuntu | apt-get update && apt-get install -y libldap-dev libsasl2-dev python3 make g++ |
| Fedora / RHEL / CentOS | dnf install -y openldap-devel cyrus-sasl-devel python3 make gcc-c++ |
| Alpine | apk add --no-cache openldap-dev cyrus-sasl-dev build-base python3 |
| macOS | brew install openldap cyrus-sasl |
| Windows (MSVC) | npm install |
On macOS, Homebrew openldap is keg-only, so source builds may also need:
export CPPFLAGS="-I$(brew --prefix openldap)/include"
export LDFLAGS="-L$(brew --prefix openldap)/lib"Source build
Linux/macOS source build needs:
- Node.js 20+
- Python 3
- C/C++ build toolchain
- OpenLDAP client headers and libraries (
ldap.h,libldap,liblber) - Cyrus SASL runtime for SASL/GSSAPI flows
Then run:
npm install
npm run build
npm testThe included build helper points node-gyp at the current Node installation so it can work in offline or air-gapped environments where Node headers are already present locally.
Windows source build
The repository now uses the standard node-gyp + MSVC toolchain on windows-latest. For local Windows source builds:
- use Developer PowerShell for Visual Studio 2022, or install the Visual Studio Build Tools workload that provides
cl.exe,MSBuild, and the Windows SDK - use Node.js 20+ and Python 3 on
PATH - run
npm install
The Windows addon links against the system Wldap32 SDK, so it does not require separate OpenLDAP or Cyrus SASL development packages.
GSSAPI / Kerberos environments
If you use bind('GSSAPI'), the base libsasl2 library is not enough on its own. You also need:
- a SASL GSSAPI plugin for your distribution
- Kerberos client libraries and configuration
- valid credentials, keytabs, or ticket cache for the target environment
Typical extra packages include:
- Debian / Ubuntu:
libsasl2-modules-gssapi-mit,krb5-user - Fedora / RHEL / CentOS:
cyrus-sasl-gssapi,krb5-workstation - Alpine:
cyrus-sasl-gssapiv2,heimdal
Build outputs
build/Release/ldap_native.node— native addontypes/— generated declaration files fromsrc/types/prebuilds/<platform-triple>/ldap_native.node— staged prebuild layout
Usage
Simple bind
const { Client } = require('ldap-native');
const client = new Client({
url: 'ldap://ldap.example.com:389',
connectTimeout: 5000,
timeout: 5000,
});
await client.startTLS({ caFile: '/etc/ssl/certs/ldap-ca.pem' });
await client.bind('cn=admin,dc=example,dc=com', 'admin');
const { searchEntries } = await client.search('dc=example,dc=com', {
filter: '(uid=jdoe)',
attributes: ['cn', 'uid', 'mail'],
paged: { pageSize: 100 },
});
await client.unbind();LDAP attributes are multi-valued by design, so search attributes are returned as arrays even when your directory currently stores only one value. If your application wants scalar values for known single-value fields, opt in per search:
const { searchEntries } = await client.search('dc=example,dc=com', {
filter: '(uid=jdoe)',
attributes: ['cn', 'uid', 'mail', 'memberOf'],
singleValueAttributes: ['cn', 'uid', 'mail'],
trimAttributeValues: ['cn', 'uid', 'mail'],
});
console.log(searchEntries[0].mail); // '[email protected]'
console.log(searchEntries[0].memberOf); // still an arraysingleValueAttributes: true is also supported and flattens any returned
attribute that has exactly one value. Prefer the explicit list for production
code because attributes such as memberOf, objectClass, proxyAddresses,
and servicePrincipalName can be legitimately multi-valued even when a
particular entry currently has one value.
trimAttributeValues follows the same shape: pass an attribute list to trim
only known text fields, or true to trim every returned string value. Buffers
are left unchanged.
Kerberos / GSSAPI
const { Client } = require('ldap-native');
const client = new Client({
url: 'ldap://ldap.example.com:389',
sasl: { mechanism: 'GSSAPI' },
});
await client.startTLS({ caFile: '/etc/ssl/certs/ldap-ca.pem' });
await client.saslBind();
const whoami = await client.exop('1.3.6.1.4.1.4203.1.11.3');
console.log(Buffer.isBuffer(whoami.value) ? whoami.value.toString('utf8') : whoami.value);
await client.unbind();bind('GSSAPI') remains available as a migration-friendly shorthand. New code
should prefer saslBind() because it maps directly to the multi-step OpenLDAP
SASL flow used by ldapsearch -Y GSSAPI.
Kerberos credentials are acquired before the LDAP bind, usually with kinit:
export LDAP_URL='ldap://ad01.example.com:389'
export LDAP_BASE_DN='dc=example,dc=com'
export LDAP_CA_FILE='/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem'
export LDAP_STARTTLS=1
export KRB5_CONFIG='/etc/krb5.conf'
export KRB5CCNAME="FILE:/tmp/krb5cc_$(id -u)"
export KRB5_PRINCIPAL='[email protected]'
export KRB5_PASSWORD='YourStrongPassword'
printf '%s\n' "$KRB5_PASSWORD" | kinit "$KRB5_PRINCIPAL"
node node_modules/ldap-native/examples/gssapi-rhel9.cjsFor service workloads, use KRB5_KEYTAB instead of KRB5_PASSWORD:
export KRB5_PRINCIPAL='[email protected]'
export KRB5_KEYTAB='/etc/security/keytabs/svc_ldap.keytab'
node node_modules/ldap-native/examples/gssapi-rhel9.cjsIf ldapsearch works but a Node client appears to send only the literal
GSSAPI mechanism name in step 1, the bind flow is wrong. ldap-native uses
OpenLDAP's multi-round interactive SASL bind path so the first request can carry
the generated GSSAPI credential token, matching the mature ldapsearch /
OpenLDAP behavior.
For a RHEL 9 oriented end-to-end example including Kerberos ticket acquisition,
TLS, npm package usage, and ldapsearch comparison, see:
On Windows, GSSAPI uses the native Wldap32 SSPI/Negotiate path instead of
Cyrus SASL. If user and password are omitted, the current Windows logon
credentials are used:
const client = new Client({
url: 'ldap://ad01.example.com:389',
sasl: { mechanism: 'GSSAPI' },
});
await client.startTLS();
await client.saslBind();For explicit Windows credentials, pass domain or realm with the account:
await client.saslBind({
mechanism: 'GSSAPI',
user: 'svc_ldap',
password: process.env.LDAP_GSSAPI_PASSWORD,
domain: 'EXAMPLE',
});The Windows verification entry point is:
$env:LDAP_URL = 'ldap://ad01.example.com:389'
$env:LDAP_BASE_DN = 'dc=example,dc=com'
$env:LDAP_GSSAPI_USER = 'svc_ldap'
$env:LDAP_GSSAPI_PASSWORD = 'secret'
$env:LDAP_GSSAPI_DOMAIN = 'EXAMPLE'
npm run test:gssapi:windowsThat command runs the Windows example and the gated integration test at
tests/integration/windows-gssapi.integration.test.cjs.
When GitHub Actions does not have Windows LDAP / Kerberos credentials, the
gssapi-windows job falls back to a self-contained synthetic LDAP fixture. That
fallback does not prove a real KDC exchange, but it does build the Windows
native addon and drives client.saslBind({ mechanism: 'GSSAPI' }) through the
Wldap32 SSPI/Negotiate code path without external secrets.
Advanced SASL bind
const { Client } = require('ldap-native');
const client = new Client({
url: 'ldap://ldap.example.com:389',
});
await client.startTLS({ caFile: '/etc/ssl/certs/ldap-ca.pem' });
await client.saslBind({
mechanism: 'PLAIN',
user: 'test_user',
password: 'secret',
realm: 'EXAMPLE.COM',
proxyUser: 'u:test_admin',
securityProperties: 'none',
});
await client.unbind();Repository layout
For a fuller annotated tree and contributor guide, see docs/REPOSITORY_LAYOUT.md.
.
├─ .github/workflows/ CI and release automation
├─ docs/ architecture, build, compatibility, testing, release notes
├─ examples/ runnable smoke / usage examples
├─ native/ Node-API addon backed by OpenLDAP libldap
├─ prebuilds/ staged native binaries for publish / install
├─ scripts/ build, packaging, and upstream-test runners
├─ src/ runtime implementation and type-declaration source
├─ tests/ unit, parity, and integration tests
├─ types/ generated declaration output published with the package
├─ upstream/ vendored upstream ldapts tests executed directly
├─ upstream-src/ vendored upstream source reused for helper parity
├─ binding.gyp native addon build definition
├─ index.cjs|mjs|d.ts public package entrypoints
└─ package.json exports, scripts, and publish metadata
Generated during build or release:
- types/
- build/
- prebuilds/Build and packaging flow
src/types/**/*.ts -----------(tsc)--------------------------> types/**/*.d.ts
native/addon.cc + binding.gyp --(node-gyp)------------------> build/Release/ldap_native.node
build/Release/ldap_native.node --(create-prebuild)---------> prebuilds/<platform-triple>/
root entrypoints + src/ + types/ ---------------------------> npm package contents
release workflow artifacts ---------------------------------> assembled prebuilds/ for npm publishWhen changing behavior, prefer editing the source-of-truth layers (src/, native/, scripts, tests, docs) instead of editing generated outputs by hand.
Test matrix
Run package tests:
npm testRun the current full local regression path:
npm run test:fullRun direct upstream coverage only:
npm run test:upstreamRun Docker-backed integration coverage for the full tests/integration/ directory:
npm run test:dockerRun optional real OpenLDAP integration tests against your own environment:
LDAP_URL='ldap://127.0.0.1:389' \
LDAP_BIND_DN='cn=admin,dc=example,dc=com' \
LDAP_BIND_PASSWORD='admin' \
LDAP_BASE_DN='dc=example,dc=com' \
npm run test:integrationFor API-compatibility CI, the repository also supports LDAP_NATIVE_USE_MOCK=1, which switches the runtime to the in-repo mock backend instead of a compiled addon.
Documentation map
- Repository layout
- Migration guide
- Architecture
- Build notes
- Compatibility matrix
- GSSAPI / Kerberos / TLS npm usage
- Kerberos notes
- Testing
- Prebuild strategy
A useful reading order for new contributors is:
- this README
docs/REPOSITORY_LAYOUT.mddocs/ARCHITECTURE.mddocs/BUILD.mdanddocs/TESTING.md
Automated CI and npm release
The repository includes two GitHub Actions workflows:
ci.yml— API compatibility tests on Linux/macOS/Windows, plus native builds and smoke tests on supported platformsrelease.yml— multi-platform prebuild collection and npm publish onv*tags
For release automation details, inspect .github/workflows/release.yml.
