hckr.fyi // thoughts

Working with ActiveDirectory in Node.JS The Basics

by Michael Szul on

Getting fully invested in the Node.JS ecosystem when you come from the Windows and ASP.NET side of things can end up with you hitting a few roadblocks if you're used to certain Microsoft technologies. Luckily, not only has Microsoft invested heavily in the Node.JS ecosystem, but so has the community with a wide range of packages to help you interact with Microsoft technology.

Among these technologies: ActiveDirectory.

ActiveDirectory is a mainstay of any Microsoft enterprise, and if you're going to build applications in the enterprise on top of Microsoft infrastructure, you'll need to interact with ActiveDirectory in some form or another.

Let's take a look at one of the community packages that can help with this: Namely the activedirectory package.

Note: This post uses TypeScript, but the examples will work with the equivalent JavaScript code.

Start by importing the package:

import * as ad from 'activedirectory';
    

You'll also need to define a configuration to pass into the ActiveDirectory object. This configuration can take a number of items. We'll go over a brief few:

const config: any = {
            url: process.env.ADCONNECTION,
            baseDN: process.env.ADDOMAIN,
            username: process.env.ADUSER,
            password: process.env.ADPASSWORD,
            includeMembership: ['user', 'group'],
            secure: true,
            timeLimit: 200,
            filter: 'YOUR SEARCH FILTER HERE',
            attributes: [
                'cn',
                'uid',
                'description',
                'ou',
                'telephoneNumber',
                'mobile',
                'title',
                'description',
                'sn',
                'givenName',
                'initials',
                'displayName',
                'isMemberOf'
            ]
        };
    

A couple of important things here: First, you'll store your connection information in this configuration, so you're probably going to end up pulling these values from environment variables. Second, I'm including user and group memberships. If you have custom memberships, you might want to add them to the includeMembership array. For filter, remember that this is going to be ActiveDirectory selection syntax, so if we're looking up a record for a single user, it might look something like this:

(&(objectClass=person)(&(uid=michaelszul)))
    

In this example, I've stored the connection information in a .env file and have used the dotenv package to add them to the processes environment variables object. Your .env could look something like this:

ADCONNECTION=LDAPS://activedirectory.example.com:636
    ADDOMAIN=OU=PEOPLE,O=EXAMPLE LLC,C=US
    ADUSER=cn=appadmin,ou=ADMINISTRATORS,o=EXAMPLE LLC,c=US
    ADPASSWORD='YOUR PASSWORD HERE'
    

Once you have your configuration, you can create the ActiveDirectory object:

const acdir = new ad(config);
    

…and with this, you can then use find() to search ActiveDirectory with the filter to bring back a person record:

acdir.find(config, (err: string, results: any): void => {
        if(err != null) {
            console.error(err);
        }
        const user: any = results.other[0];
        const memberships: string[] = (typeof(user.isMemberOf) === 'string') ? [].concat([user.isMemberOf]) : user.isMemberOf;
        const groups: string[] = memberships.map((f: string): string => f.split(',')[0].replace('cn=', ''));
        //Do something with the groups
    });
    

In this example, we pass the configuration to the find() method, check for any errors, and then process the ActiveDirectory data. We get our user information from the other array, but property and mileage for you will vary. For this particular code snippet, we're getting the groups of a user, so we process the memberships in order to acquire those groups.

One thing to note is that this uses the traditional event-driven callback scheme of JavaScript. You're probably going to want to turn this into a Promise:

export function getGroups(config: any, acdir: any): Promise<string[]> {
        return new Promise((resolve, reject): void => {
            try {
                acdir.find(config, (err: string, results: any): void => {
                    if(err != null) {
                        reject(err);
                    }
                    const user: any = results.other[0];
                    const memberships: string[] = (typeof(user.isMemberOf) === 'string') ? [].concat([user.isMemberOf]) : user.isMemberOf;
                    const groups: string[] = memberships.map((f: string): string => f.split(',')[0].replace('cn=', ''));
                    resolve(groups);
                });
            }
            catch(e) {
                reject(e);
            }
        });
    }
    

Apologies to TypeScript purists for so many any types. The activedirectory package does not have a typings file.