Oleg's software

NodeJS authentication and user management with OpenLDAP in 4 simple steps.

In this article we will write a node.js module which will provide registration, authentication and password management services with OpenLDAP as a backend. Let's see how modern software can collaborate with old-school systems. Node.js + OpenLDAP + Docker + OpenShift

Why NodeJS?

Node.js is the best bet in API economy, because it is easy in development, fast in production, requires less resources and provides efficient solutions. And last but not least, it is modern and makes you think in a way modern applications are created. The price is scarce in structures variety, messy variable types, lack of available abstractions and 100 and 1 way to shoot yourself in the foot. Anyway Node.js delivers great results it’s very specific field and we can refer to the giants and their experience:

The version of Node.js which is used in this article is 6.2.1:

  $ node --version
  v6.2.1

Why LDAP?

LDAP is secure, reliable and wide spread protocol for storing user directory and organizing single sign on point. It is supported by abundance of applications and operating systems. Moreover it is a de facto standard in enterprise world. These all makes it’s implementation – OpenLDAP server – perfect backend solution for our project.

Environment

How many developers do we need for our Node.js+OpenLDAP project? If we had chosen Java it would have been taken 20 developers, database administrators and systems administrator to complete the project. But with Node.js all we need is one full-stack developer who also has got DevOps among his skills. And we will spend less on deployment and more on delivering so OpenShift, with it’s great Pods and Images abstractions, is an excellent choise for us. However there are limitations. For example OpenLDAP image (I use osixia image which can be forked and easily tweaked with cusom LDIFs) can not be deployed to OpenShift because it requires root priveleges to be installed. There are projects to hack this behavior, but unfortunately they are still not production ready.

Starting development

Scope

In this project we will implemement these services:

Implementation

Firstly, it is important to understand that to communicate with OpenLDAP server node.js service has to send text messages over TCP using sockets. Sockets management is not a job that can be maintained in any language and node.js is not an exclusion. So do not reinvent the weel and jump stright to open source solutions. My personal favorite is ldapjs created by former Joyent vice president of engineering Mark Cavage. Mark now works for Oracle and Joyent is a part of Samsung. So big names here. Apart from this ldapjs is most capable solution in open source team.

All right, let’s start coding! To begin with, we need to establish connection to OpenLDAP, after that we can start send and receive messages. Communication with OpenLDAP works in session essentialy in contrast with RESTful API that we are building.

We will connect using these few lines of code:

As I already mentioned, LDAP fundamentally sessional and synchronous software and it is so old so it can’t bear multithreading properly, whereas Node.js is asynchronous thanks to libuv. You can learn more about multithreaded clients to LDAP here and I will just mention that client shall not mix several sessions in one connection. Ldapjs solves this problem for us (that is why I love it) and we required only to relax, drink some puerh tea and do not try to reuse client object created by ldapjs module in different endpoints. Just always create new one.

Registration

On this step we will add a user to an LDAP Directory. Let’s consider this code snippet:

First of all, let’s wrap our code with Promise as ldapjs uses callbacks and can’t be promisified automatically. Take in account that ldapjs uses Sockets at the backend and Sockets use EventEmitter to return results. In real life this means ldapjs.bind() will return true instantly and callback will be called later. So make sure that you resolve your promise explicitly calling Promise.resolve() inside your callback (for example, Bluebird.try() is not suitable as it does not require explicit resolve).

Step 1. We build new client object to communicate with LDAP server. In turn ldapjs opens socket, establishes connection and add it to a pool.

This operation does not use callbacks and all errors will be emited as events. You can catch ‘em all using the code below:

Accordingly while developing centralized logging sub-system it is important to consider these as methods of error delivering.

Step 2. bind operation is performed, which authenticate a user in LDAP server and establishes priveleged session. As we create a new user we should authenticate as a user administrator. After that our session wil have a right to add record to a directory.

Step 3. Add new user to a server. Depending on your OpenLDAP schema for objectClasses this operation may require to specify unique cn (common name of an entity) and may be have givenName and familyName fields. Available attributes is provided by objectClass which inherited by a new record. We inherit three at once — person, organizationalPerson and inetOrgPerson.

pwdPolicySubentry will keep a link to a record of objectClass pwdPolicy containing a list of password policy rules, such as password history, lockout, min length, etc. Actual password will be stored in a field designated by an actual policy. In our case it is userPassword.

To add a user we specify DN Distinguished Name and a dictionary with pairs attribute:value. Empty err in callback cause fulfill promise.

You may notice that we do not unbind after the end of transaction. It is perfectly OK and not a bug as it will not cause any issues with OpenLDAP and connection is managed for you by ldapjs. You may test it and notice that unbind will not close the connection for you, nor do any other finishing activity. However it is a good idea to unbind to release OpenLDAP resources.

Authentication

As usual we use Promises and do bind inside to establish priveleged session with specified login and password. If it was successful and a the login-password pair was correct then the promise will be resolved otherwise it will be rejected.

Change password

To modify existing records we need to build a special object Change in which we will incapsulate operation name and fields with new values that we want to update.

As this operation is meant to be done by a user to change her own password it is supposed that an old password is known. Therefore at first we prove this to be true binding the user (step 1) and as result establishing a session with priveledge to modify the password.

On the step 2 we create modification object with operation property set to “replace”. This operation type does not require us to specify old password in Change object as we already know that it is correct because of the successful bind above. Other option is to use operation ‘modify’ which will shift responsibility to check old password to OpenLDAP. Either way Change object will be trasformed by ldapjs to a message to LDAP server and if it is successful at the end we will have a fullfilled promise.

If a user forgot her password then it is impossible for her to authenticate so the code should be executed using secure context of a third party who has priveleges to change password, i.e. user admin account. All remaining logic stays the same. Please note that it is discouraged to do this operation as a RootDN for a few reasons. One of them is that it will not affect password policy history rule, which means new password can be the same as previous even if you define a rule to not create same passwords.

Retrieve

And finally let’s finish our module with search records method which can be helpful for administration purposes.

So after creating a client we bind as a manager user (step 1) to gain priveleges to access all records in directory (regular users can acceess to only their own record).

Step 2 - we construct a search options object (please refer to ldapsearch for documentation). Please note how we specify here hidden system fields so they will be available in results.

Step 3 - getting search results as a stream. This is the nature of OpenLDAP – not an atomic response but a stream of data chunks that should be collected and stored.

Conclusion

OpenLDAP has it’s benefits as well as drawbacks. It seems complicated and intricate for newcomers, requires some learning curve to be passed, has some restrictions related to obsolete architecture and data model. From the other hand it is simple and robust for advanced users, workhorse in enterprise systems where it is required enhanced security and connect from variety of software. It seems that it still in a game even with modern players.