23

I'm trying to build one NodeJS server and planning to use the organization's Microsoft Active Directory for authentication.

I tried the same with many packages (activedirectory, activedirectory2, ldapjs etc.)

But none of them seems to work for me.

I'm supplying the LDAP URL and below is my code.

var ldapjs = require('ldapjs');

var config = { url: 'ldap://mycompany.com/dc=mycompany,dc=com'
           ,timeout: 10
           ,reconnect: {
              "initialDelay": 100,
              "maxDelay": 500,
              "failAfter": 5
              } 
        }

var username = "user_id@mycompany.com";
var password="password";

const ldapClient = ldapjs.createClient(config);


ldapClient.bind(username, password, function (err) {
console.log("Logging data...");
ldapClient.search('dc=mycompany,dc=com', function (err, search) {
 if (err) {
    console.log('ERROR: ' +JSON.stringify(err));
    return;
  }
search.on('searchEntry', function (err,entry) {
   if (err) {
    console.log('ERROR: ' +JSON.stringify(err));
    return;
  }
  else{
    var user = entry.object;
    console.log("Done.");
    return;
   }

   });
  });
});

Sometimes it works, but for most of the times I keep on getting following error (may be when it chooses a different IP)

Error: connect ETIMEDOUT <ip address>:389
at Object.exports._errnoException (util.js:1018:11)
at exports._exceptionWithHostPort (util.js:1041:20)
at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1090:14)

What puzzles me is; if I try with the same LDAP URL in my C# application, it works fine.

Is there a difference in the way .Net app uses it than the way NodeJS uses?

Can I change my code in some way to make it work?

A3006
  • 1,051
  • 1
  • 11
  • 28

6 Answers6

19

Because this is the first question that pops up in Google's search result, and it took me quite some time to figure out how to use Active Directory Authentication, I'm going to share the solution from This tutorial.

It was very easy to understand and implement comparing to other examples I've found on the internet:

npm install --save activedirectory

// Initialize
var ActiveDirectory = require('activedirectory');
var config = {
    url: 'ldap://dc.domain.com',
    baseDN: 'dc=domain,dc=com'
};
var ad = new ActiveDirectory(config);
var username = 'john.smith@domain.com';
var password = 'password';
// Authenticate
ad.authenticate(username, password, function(err, auth) {
    if (err) {
        console.log('ERROR: '+JSON.stringify(err));
        return;
    }
    if (auth) {
        console.log('Authenticated!');
    }
    else {
        console.log('Authentication failed!');
    }
});

The most difficult part was to figure out what suffix to use for the username.

I was getting the error:

ERROR: {"lde_message":"80090308: LdapErr: DSID-0C090400, comment: AcceptSecurityContext error, data 52e, v1db1\u0000","lde_dn":null}

Before finally setting the right suffix, for me it was something like:
var username = 'john.smith@foo.companyname.com

Alex Weitz
  • 3,199
  • 4
  • 34
  • 57
  • 2
    Hi Alex. What are username and password in your code? username and password provided by user who is being authenticated or something else? – Himanshu Singh Jan 28 '21 at 15:35
  • 1
    @HimanshuSingh Yes, the username and password provided from the user. In my code it looks something like this: `exports.login = (username, password, next) => {ad.authenticate(username + "@,my.company.com", password, (err, auth) => {...` – Alex Weitz Jan 29 '21 at 00:41
  • Hmm.. I wonder if it could be said an SSO then. My understanding and expectation while setting up an SSO with AD was completely different. If user of my app have to pass username and password again then I am not sure if this could be counted as SSO. Could you maybe have a look at question I posted and give me some insights? https://stackoverflow.com/questions/65936881/authentication-flow-for-sso-between-a-node-js-react-app-and-microsoft-active Your help is very much appreciated :) – Himanshu Singh Jan 29 '21 at 04:16
  • Sorry, no idea how to help you. The answer provided here only answers the question "How do you authenticate an AD username+password via AD service" – Alex Weitz Jan 29 '21 at 06:38
  • @HimanshuSingh it is not, the password is vulnerable to mim and certificate trickery, a secure authentication would have the user authenticate directly to the AD, authorize the app, and pass a revokable & expiring token to the service. – Tamir Daniely Apr 06 '21 at 09:21
  • 1
    This work for me. IP address is also enough in ldap url. var config = { url: 'ldap://ip address' }; var username = 'username@domain.local'; var password = 'PASSWORD'; – Raju Jun 11 '21 at 09:00
  • Tried this but I'm getting "TypeError: Cannot read properties of undefined (reading 'on')" from within ldapjs (which is used by activedirectory under the hood). Any idea why? – Caleb Koch Mar 17 '22 at 17:27
4

I got this working by first getting the username that made the request with npm:express-ntlm. Then with this information, I use npm:activedirectory to query Active Directory for that user's details.

app.use(
  ntlm({
    domain: process.env.DOMAIN,
    domaincontroller: process.env.DOMAINCONTROLLER
  })
);

...

app.use("/", authenticate, require("./routes/index"));

Inside my authenticate middleware I now have access to req.ntlm which contains

{ DomainName: '...',
  UserName: '...',
  Workstation: '...',
  Authenticated: true }

I setup the ActiveDirectory object, and note "bindDN" and "bindCredentials" instead of "username" and "password":

var ad = new ActiveDirectory({
  url: process.env.DOMAINCONTROLLER,
  baseDN: process.env.BASEDN,
  bindDN: process.env.USERNAME,
  bindCredentials: process.env.PASSWORD
});

Then you can use the ad object like in the npm:activedirectory documentation:

ad.findUser(req.ntlm.UserName, (err, adUser) => {
    ...
});

findUser returns things like first and last name, email address, which is all I needed but you could easily look into groups.

Gabriel
  • 342
  • 4
  • 12
  • Probably want to point out that the bind user is not the user logging in, but rather a service user with permissions to make the required membership queries. – Tamir Daniely Apr 06 '21 at 09:26
  • And still even this is considered vulnerable today, oauth / saml would offer much better security. – Tamir Daniely Apr 06 '21 at 09:29
2

The configuration object specifies a 10 millisecond timeout. That seems pretty short. Were you going for a 10 second timeout?

JS Documentation

Are you using the LdapConnection.Timeout object in C#? That one expects seconds.

C# Documentation

thestud2012
  • 69
  • 1
  • 3
0
var ActiveDirectory = require('activedirectory');
        
var username = 'uid='+req.body.username+',cn=users,dc=domain,dc=com';
var password = req.body.password;
    
var ad = new ActiveDirectory({
  "url": "ldap://domain.com",
  "baseDN": "dc=domain,dc=com"
});
    
ad.authenticate(username, password, function (err, auth) {
  if (err) {
    res.send({ 
      status:404,
      'msg':'Wrong Credential!!',
      'data':false
    })
  }
  if (auth) {
    console.log('Authenticated from Active directory!');
    res.send({ 
      status:200,
      'msg':'Success',
      'data':true
    })
  }
});

The username need to be bind with base dn. e.g ='uid=john,cn=users,dc=domain,dc=com'.

It worked in NodeJS version above 10. Active directory package won't support for the node version <= 9.

Tyler2P
  • 2,324
  • 26
  • 22
  • 31
0

We can use bind method to authenticate user in the following way.

/*update the url according to your ldap address*/
var client = ldap.createClient({
    url: "ldaps://127.0.0.1:636",
    tlsOptions: {
      ca: [
        fs.readFileSync("./chain.pem")
      ],
    },
  });

/*use this to create connection*/
function authenticateDN(username, password) {

    /*bind use for authentication*/
    client.bind(username, password, function (err) {
        if (err) {
            console.log("Error in new connetion " + err)
        } else {
            /*if connection is success then go for any operation*/
            console.log("Success");
        }
    });
}

/*create authentication*/
authenticateDN("uid=admin,ou=system", "secret")
KARTHIKEYAN.A
  • 18,210
  • 6
  • 124
  • 133
-1

I suspect what you're experiencing is the fact that active directory (AD) is highly available and not always 100% of domain controllers are online. C# seems to have some method to detect which are not online possibly?

I have had similar experiences when using the HA Name for the domain which it looks like you did.

In these cases it helps to speak to your AD Administrator and get the AD Server that is supposed to be servicing the site your servers are in and speak directly to that one via IP Address or DNS.

Hope that helps.