I am creating a login authentication page, where a user would input there active directory username and password and using NodeJS I would check to see if it\'s valid, but I
In this case, you need ldapClient rather than ldapServer, this is the example code from the official doc:
var ldap = require('ldapjs');
ldap.Attribute.settings.guid_format = ldap.GUID_FORMAT_B;
var client = ldap.createClient({
url: 'ldap://127.0.0.1/CN=test,OU=Development,DC=Home'
});
var opts = {
filter: '(objectclass=user)',
scope: 'sub',
attributes: ['objectGUID']
};
client.bind('username', 'password', function (err) {
client.search('CN=test,OU=Development,DC=Home', opts, function (err, search) {
search.on('searchEntry', function (entry) {
var user = entry.object;
console.log(user.objectGUID);
});
});
});
Suggestions
1.Don't use ldapauth-fork (Huge hanging issue, if we hit multiple requests then after some time library gets unresponsive and doesn't return anything.)
2.Don't use passport-ldapauth (Internally calls ldapauth-fork)
We can use ldapjs, which has easy implementation and is based on event driven approach.
Below nodejs code explain complete solution for ldap auth and search.
JS code
const ldap = require('ldapjs');
let client
// unbind after completion of process
function closeConnection() {
console.log('closeConnection')
client.unbind(err => {
console.log('unbind error', err)
});
}
function search() {
const searchOptions = {
filter: '(uid=yourSearchText)', // search text
scope: 'sub'
};
return new Promise((resolve, reject) => {
client.search('ou=consultants,' + 'ou="Your OU",ou=yourOu,dc=yourDc,dc=com', searchOptions, (err, res) => {
res.on('searchEntry', entry => {
console.log('searchEntry', entry.object);
resolve(entry.object)
});
res.on('searchReference', referral => {
console.log('referral: ' + referral.uris.join());
resolve(referral.uris.join())
});
res.on('error', err => {
console.error('search error: ' + err.message);
reject(err)
});
res.on('end', result => {
console.log('If not found', result);
reject({ message:'User not found'})
});
});
})
}
function authenticate() {
const server = 'ldap server ip';
client = ldap.createClient({
url: `ldap://${server}`
});
return new Promise((resolve, reject) => {
client.bind('cn=yourcn,dc=yourdc,dc=com', 'sortedSolutions', err => {
if (err) {
reject(err)
}
resolve('Authenticated successfully')
});
})
}
function start(req, res) {
let searchResponseData
authenticate()
.then(authenticateResponse => {
console.log('authenticateResponse', authenticateResponse)
return search()
})
.then(searchResponse => {
console.log('searchResponsesearchResponse', searchResponse)
searchResponseData = searchResponse
return closeConnection()
})
.then(closeConnectionResponse => {
console.log('ldap connection closed', closeConnectionResponse)
res.status(200).send(searchResponseData)
})
.catch(error => {
console.log('catch error', error)
res.status(400).send(error)
})
}
module.exports.start = start
// We can use same code with no authentication, Just pass '' to bind function client.bind('', '', err => { //same as above })
@Sukh Thank you for posting your UPDATE solution; however, there is a problem with the code you posted in your UPDATE. While it works for simple cases, with larger queries, you will find you are unbinding before the results have been output. The solution for me was to move your unbinds into the search.on functions.
Here is an edit of your UPDATE:
var ldap = require('ldapjs');
ldap.Attribute.settings.guid_format = ldap.GUID_FORMAT_B;
var client = ldap.createClient({
url: 'ldap://batman.com/cn='+username+', ou=users, ou=compton, dc=batman, dc=com',
timeout: 5000,
connectTimeout: 10000
});
var opts = {
filter: '(&(objectclass=user)(samaccountname='+username+'))',
scope: 'sub',
//attributes: ['objectGUID']
// This attribute list is what broke your solution
attributes: ['objectGUID','sAMAccountName','cn','mail','manager','memberOf']
};
console.log('--- going to try to connect user ---');
try {
client.bind(username, password, function (error) {
if(error){
console.log(error.message);
client.unbind(function(error) {if(error){console.log(error.message);} else{console.log('client disconnected');}});
} else {
console.log('connected');
client.search('ou=users, ou=compton, dc=batman, dc=com', opts, function(error, search) {
console.log('Searching.....');
search.on('searchEntry', function(entry) {
if(entry.object){
console.log('entry: %j ' + JSON.stringify(entry.object));
}
client.unbind(function(error) {if(error){console.log(error.message);} else{console.log('client disconnected');}});
});
search.on('error', function(error) {
console.error('error: ' + error.message);
client.unbind(function(error) {if(error){console.log(error.message);} else{console.log('client disconnected');}});
});
// don't do this here
//client.unbind(function(error) {if(error){console.log(error.message);} else{console.log('client disconnected');}});
});
}
});
} catch(error){
console.log(error);
client.unbind(function(error) {if(error){console.log(error.message);} else{console.log('client disconnected');}});
}
At least this is what I discovered when using your solution with Active Directory searches. memberOf returns A LOT of entries in my use case and the unbinds were being done prematurely, so I was getting the following error:
error: 1__ldap://my.domain.com/,OU=Employees,OU=Accounts,DC=my,DC=domain,DC=com closed
client disconnected