0
votes

I am using the npm package activedirectory to connect to my company's domain controller for the purpose of authenticating users on an internal website. The plan is for users to enter their windows domain credentials into the website's login page (written with React). The website will forward the credentials to a backend Express server that looks like this:

const express = require('express');
const bodyParser = require('body-parser');
const ActiveDirectory = require('activedirectory');

const app = express();
app.use(bodyParser.urlencoded({
    extended: false,
}));

app.get('/ldap', (request, response) => {
    // set http header for json
    response.setHeader('ContentType', 'application/json');

    // get the username and password from the query
    const username = request.query.username + '@internal.mycompany.com';
    const password = request.query.password;
    console.log(username, password);

    // create a new active directory connection
    let activeDirectory = new ActiveDirectory({
        url: 'LDAP://internal.mycompany.com',
        baseDN: 'DC=mycompany,DC=com',
    });

    // attempt authentication
    activeDirectory.authenticate(username, password, (error, authenticated) => {
        if(error) {
            console.log('ERROR: ' + JSON.stringify(error));
        }
        else {
            console.log('Authentication successful');
        }

        // respond
        response.send(JSON.stringify({
            error: error,
            authenticated: authenticated,
        }));
    });

});

app.listen(3001, () => {
    console.log('Express server running on port 3001.');
});

The React frontend executes this when the 'Log In' button is clicked:

login() {
    console.log('authenticating with username: ', this.state.username, ' and password: ', this.state.password);
    fetch(`/ldap/?username=${encodeURIComponent(this.state.username)}&password=${encodeURIComponent(this.state.password)}`).then((response) => {
        return response.json();
    }).then((response) => {
        console.log(response);
        this.props.setLoggedIn(response.authenticated);
        this.setState({
            loginFailed: !response.authenticated,
            redirectToHome: response.authenticated,
        });
    });
}

It calls fetch to ask the Express server to authenticate the credentials, and sets some global loggedIn state accordingly. It also sets local state to display an error message if the login attempt failed or redirect the user on to the main website homepage if the login attempt was successful.

Leaving the username and password blank yields this response:

{
    error: {
        code: 49,
        description: "The supplied credential is invalid",
        errno: "LDAP_INVALID_CREDENTIALS",
    },
    authenticated: false,
}

Typing in a valid username and password yields this response:

{
    error: null,
    authenticated: true,
}

This is all as expected up to this point.

However, typing in random characters for the username and password yields one of 2 responses. It either authenticates successfully, which shouldn't happen, or it gives this:

{
    error: {
        lde_dn: null,
        lde_message: "80090308: LdapErr: DSID-0C0903A9, comment: AcceptSecurityContext error, data 52e, v1db1",
    },
    authenticated: false,
}

QUESTION: Why would giving the domain controller garbage credentials cause it to return authenticated: true? And why only sometimes? Some information must be cached or remembered somewhere. I tried restarting the Express server and I tried waiting a day for something to expire.

Note: I was planning to encrypt/scramble the passwords so that they are not being sent in plain text, but if there is an even better way to send them securely, please leave a comment about how to improve this. But for now the main question is about the incorrect Active Directory/LDAP authentication.

1
This would not explain why it's intermittent, but is the guest account enabled on that domain? - Gabriel Luci
I'm not sure, and after reading around for ~1hr, I can't find how to check. My guess is that it's not, but I don't know. I did try to query the ActiveDirectory to find a user named guest or a group named Guests, but those could have been renamed. When querying for those things, however, I was unable to get results because I got an error saying "In order to perform this operation a successful bind must be completed on the connection," even when I did it after a successful authentication. Do you know how the guest account being enabled could be contributing to the issue at hand? - nullromo
I remember reading this. The guest account is just a user account called Guest on the domain. It's in the built-in Users container at the root of the domain. The account is disabled by default. - Gabriel Luci
Hmm, that is an interesting lead. In my case however, entering a known user with a bad password still authenticates. I will see if I can get my system admin to disable the guest account. - nullromo
It's also worth noting that the activedirectory npm package hasn't been touched in years. Someone forked it and is maintaining it under the name activedirectory2. You might be better off using that package. - Gabriel Luci

1 Answers

0
votes

This information: https://tools.ietf.org/html/rfc4513#section-6.3.1 essentially tells us that getting authenticated: true may not actually mean anything. The solution I came up with is to try and query the Active Directory to see if the user that just authenticated exists and/or try doing an/some operation(s) as the newly-authenticated user. If the operation(s) fail(s), then the login was not valid.

Additionally, there was an error in the above code. baseDN should have been 'DC=internal,DC=mycompany,DC=com'.