Browse project docs

Active Directory Example

Search Active Directory safely by sAMAccountName with ldapjs-community and a constructed equality filter.

On this page

This example binds a service account to Active Directory and searches for one user by sAMAccountName. It constructs an EqualityFilter so user input is encoded by the library instead of being interpolated into LDAP filter syntax.

Link to EnvironmentEnvironment

bash
export AD_URL='ldaps://dc1.example.com:636'
export AD_BIND_DN='CN=LDAP Reader,OU=Service Accounts,DC=example,DC=com'
export AD_PASSWORD='replace-me'
export AD_BASE_DN='DC=example,DC=com'
export AD_CA_FILE='/path/to/ad-ca.pem'
export AD_USERNAME='ada'

Link to CodeCode

javascript
const fs = require('node:fs')
const ldap = require('ldapjs')

const required = ['AD_URL', 'AD_BIND_DN', 'AD_PASSWORD', 'AD_BASE_DN', 'AD_CA_FILE', 'AD_USERNAME']
for (const name of required) {
  if (!process.env[name]) throw new Error(`Missing ${name}`)
}

const client = ldap.createClient({
  url: process.env.AD_URL,
  connectTimeout: 5000,
  timeout: 10000,
  tlsOptions: { ca: [fs.readFileSync(process.env.AD_CA_FILE)] }
})

let finished = false
function close(exitCode) {
  if (finished) return
  finished = true
  client.unbind(() => {
    process.exitCode = exitCode
  })
}

client.on('error', (err) => {
  console.error('Active Directory connection error:', err.message)
  close(1)
})

client.bind(process.env.AD_BIND_DN, process.env.AD_PASSWORD, (bindError) => {
  if (bindError) {
    console.error('Service bind failed:', bindError.message)
    close(1)
    return
  }

  const filter = new ldap.EqualityFilter({
    attribute: 'sAMAccountName',
    value: process.env.AD_USERNAME
  })

  client.search(process.env.AD_BASE_DN, {
    scope: 'sub',
    filter,
    attributes: ['dn', 'displayName', 'mail', 'userPrincipalName'],
    sizeLimit: 2
  }, (searchError, response) => {
    if (searchError) {
      console.error('Search could not start:', searchError.message)
      close(1)
      return
    }

    const entries = []
    response.on('searchEntry', (entry) => entries.push(entry.object))
    response.on('error', (err) => {
      console.error('Search failed:', err.message)
      close(1)
    })
    response.on('end', (result) => {
      if (result.status !== 0) {
        console.error('LDAP search status:', result.status)
        close(1)
        return
      }

      if (entries.length !== 1) {
        console.error(`Expected one user, found ${entries.length}`)
        close(1)
        return
      }

      console.log(entries[0])
      close(0)
    })
  })
})

Attribute availability and search permissions depend on the directory schema and account privileges. Confirm them with the Active Directory operator rather than assuming every deployment exposes the same fields.