Using LDAPS in Meteor

I decided to undertake the semi-daunting task of creating a small Meteor application for my job. The underlying concepts are simple:

  1. Allow users to login with their company SSO
  2. Show real-time metrics of customer callbacks
  3. Allow authenticated users to create and claim callbacks

A "callback" in this instance is when a customer calls in and we unfortunately have nobody available to take the call. We then create a callback (pardon the ambiguity) that contains their account information, contact number, and any additional notes about their reason for calling support.

All of this is easily handled within Meteor by default -- except the SSO authentication. Meteor doesn't exactly seem well equipped for this kind of internal enterprise solution, but the tools are out there for anyone willing to hack something together.

These are some resources that I utilized heavily:

Extending Meteor Accounts


ldapjs Documentation

Meteor's accounts_server.js

The basic idea for LDAP authentication goes like this:

  1. Register a new login handler for the built-in Meteor package accounts-base (as of Meteor 0.7.2 we only need to return "undefined" or a "login object" from this handler -- token generation is done for us)
  2. Pass the username and password from the login form into a function that binds against the internal LDAP server
  3. If the bind succeeds then pull/create a Meteor user and return it. Otherwise, our client-side login method can set a session state to let the user know that authentication failed.

Now I'll cut to the chase and show what I have so far. Note: ldapjs does not use the LDAP TLS extended operation, so a SSL certificate is required for a secure connection. If your LDAP maintainer does not provide a certificate then you'll have to figure something else out.

Server side:
Client side:

Drop this bit of LiveScript into Meteor (or the compiled JS) and you'll get a very basic but working LDAPS authentication. But what else needs to be done?

  1. Secure the password that is sent from the client to the server.

    • Solution 1: Put Meteor behind a SSL proxy like nginx

    • Solution 2: Encrypt the password before it reaches the server

  2. Pull more fields from the LDAP server to use for user information

    • Solution: Use the entry.object properties to retrieve more fields in the search.on callback. Pass these into an object that can be read by the Meteor.Accounts user generation.
  3. Report errors on failed authentication

    • Solution: At the moment the error catching is very limited. The LDAP.client.bind error object needs to be wrapped in a block that can send the actual LDAP error to the client. I have only been able to get the actual error message so far with assert.ifError(err), but admittedly I have not done much more with this yet.

At the moment I am catching all errors in a very basic "Login failed" session state that gets reset upon each new attempt. It works for now.