adauth

Authenticate against an Active Directory domain via LDAP

Usage no npm install needed!

<script type="module">
  import adauth from 'https://cdn.skypack.dev/adauth';
</script>

README

node-adauth

Fork of node-ldapauth-fork targeted towards use with an Active Directory domain.

Usage

import ADAuth from 'adauth'

const options = {
  url: 'ldaps://corp.example.com:636',
  domainDN: 'dc=example,dc=com',
}

const auth = await ADAuth.create(options)

// or

const auth = new ADAuth(options)
await auth.initialise()

try {
  const user = await auth.authenticate(username, password)
} catch (error) {
  console.error('Authentication failed: ', error)
}

await auth.dispose()

ADAuth inherits from EventEmitter.

Install

$ npm install adauth

ADAuth Config Options

Required client options:

  • url - LLDAP url for the AD domain controller, e.g. ldaps://corp.example.com:636
  • domainDN - The root DN of the AD domain, e.g. dc=corp,dc=example,dc=com

Configuration

  • searchBase - The base DN from which to search for users by username. E.g. ou=users,dc=example,dc=com
  • searchFilterByDN - Optional, default (&(objectCategory=user)(objectClass=user)(distinguishedName={{dn}})). Search filter with which to find a user by FQDN.
  • searchFilterByUPN - Optional, default (&(objectCategory=user)(objectClass=user)(userPrincipalName={{upn}})). Search filter with which to find a user by UPN. (user@domain.com)
  • searchFilterBySAN - Optional, default (&(objectCategory=user)(objectClass=user)(samAccountName={{username}})). Search filter with which to find a user by their old-format Windows username.
  • searchAttributes - Optional, default all. Array of attributes to fetch from LDAP server.
  • bindProperty - Optional, default dn. Property of the LDAP user object to use when binding to verify the password. E.g. name, email
  • searchScope - Optional, default sub. Scope of the search, one of base, one, or sub.

adauth can look for valid user groups too. Related options:

  • groupSearchBase - Optional. The base DN from which to search for groups. If defined, also groupSearchFilter must be defined for the search to work.
  • groupSearchFilter - Optional. LDAP search filter for groups. Place literal {{dn}} in the filter to have it replaced by the property defined with groupDnProperty of the found user object. {{username}} is also available and will be replaced with the uid of the found user. This is useful for example to filter PosixGroups by memberUid. Optionally you can also assign a function instead. The found user is passed to the function and it should return a valid search filter for the group search.
  • groupSearchAttributes - Optional, default all. Array of attributes to fetch from LDAP server.
  • groupDnProperty - Optional, default dn. The property of user object to use in {{dn}} interpolation of groupSearchFilter.
  • groupSearchScope - Optional, default sub.

Other adauth options:

  • includeRaw - Optional, default false. Set to true to add property _raw containing the original buffers to the returned user object. Useful when you need to handle binary attributes
  • cache - Optional, default false. If true, then up to 100 credentials at a time will be cached for 5 minutes.
  • log - Bunyan logger instance, optional. If given this will result in TRACE-level error logging for component:ldapauth. The logger is also passed forward to ldapjs.

Optional ldapjs options, see ldapjs documentation:

  • tlsOptions - Needed for TLS connection. See Node.js documentation
  • socketPath
  • timeout
  • connectTimeout
  • idleTimeout
  • strictDN
  • queueSize
  • queueTimeout
  • queueDisable

How it works

The AD authentication flow is usually:

  1. Bind the client using the given username and credentials to verify the given password
  2. Use the client to search for the user by substituting {{username}} from the appropriate searchFilter
  3. Search for the groups of the user

express/connect basicAuth example

import basicAuth from 'basic-auth'
import ADAuth from 'adauth'

const ad = await ADAuth.create({
  url: "ldaps://corp.example.com:636",
  domainDN: "DC=example,DC=com",
  searchBase: "OU=Users,OU=MyBusiness,DC=example,DC=com",
  tlsOptions: {
    ca: "./example-ca.cer",
  },
  reconnect: true,
})

const rejectBasicAuth = res => {
  res.statusCode = 401
  res.setHeader('WWW-Authenticate', 'Basic realm="Example"')
  res.end('Access denied')
}

const basicAuthMiddleware = (req, res, next) => {
  const credentials = basicAuth(req)
  if (!credentials) {
    return rejectBasicAuth(res)
  }

  ad.authenticate(credentials.name, credentials.pass)
    .then(user => {
      req.user = user
      next()
    })
    .catch(error => rejectBasicAuth(res))
  })
}

License

MIT