meanjs

Mean.js is a new popular Javascript full stack using MongoDB for persistence. To be more precise it incorporates MongoDB, ExpressJS, AngularJS, and Node.js and allows for very fast and light-weight application development. The use of scaffolder Yeoman boosts creation of modules, routes, controller, views and other boilerplate Javascript code. As a result, a Single Page Application can be generated very fast. In addition a a user module with Passport authorization supporting a local and several social backend strategies is included.

In order to leverage the the backend authorization capabilities to be used ineth enterprise environment, I combined it with LDAP authorization backend. Since  passport-ldapauth implements this problem it was natural to use this component. In the following post,  I provide the configuration needed for the implementation, inspired by the author of the module.

LDAP Strategy

First of all, the LDAP backend needs to be implemented. For this purpose, the following dependencies needs to be added:

  "dependencies": {
    ...
    "ldapjs": "mcavage/node-ldapjs",
    "passport-ldapauth": "0.3.0",    
  }

Then create a simple LDAP strategy and put in to users/config/strategies aslong with other existing strategies:

'use strict';

var passport = require('passport'),
    LdapStrategy = require('passport-ldapauth'),
    User = require('mongoose').model('User'),
    config = require('../config');

var loginSuccess = function(userLdap, done) { ... };

module.exports = function() {
    passport.use(new LdapStrategy({
      server: {
        url: config.ldap.url,  
        bindDn: config.ldap.bindDn,
        bindCredentials: config.ldap.bindCredentials,
        searchBase: config.ldap.searchBase,
        searchFilter: config.ldap.searchFilter,
        searchAttributes: config.ldap.searchAttributes
      },
      usernameField: 'username',
      passwordField: 'password'
    }, loginSuccess
  ));

  passport.serializeUser(function(user, done) {
    done(null, user._id);
  });
  
  passport.deserializeUser(function(id, done) {
    User.findOne({
      _id: id 
    }).exec(function(err, user) {
        done(err, user);
      });
    });
};

I used configuration object to provide stage-specific values for LDAP connection. In case of login success, the user profile from LDAP can be mapped to the user by the loginSuccess function:

var loginSuccess = function(userLdap, done) {
      User.findOne({
        username: userLdap.uid
      }, function(err, user) {
        if (err) {
          return done(err);
        }
        if (!user) {
          user = new User({
              firstName: userLdap.givenName,
              lastName: userLdap.sn,
              username: userLdap.uid,
              displayName: userLdap.displayName,
              email: userLdap.mail,
              provider: 'ldap'
          });
          user.save(function(err) {
            return done(err, user); // Error happens here
          });
        } else {
          return done(err, user); // Error happens here
        }
      });
    };

If the user with specified doesn’t exist, a new user is created using attributes from LDAP structure and stored as a local profile.

Using the LDAP Strategy during sign-in

The users.authentication.server.controller.js contains code responsible for the receipt of the authentication request from the client and executing the Passport functions. Our LDAP strategy must be called from that code:

/**
 * Signin after passport authentication
 */
exports.signin = function(req, res, next) {

	if (config.strategy === 'ldapauth') {
		/*
		 * LDAP
		 */
		passport.authenticate('ldapauth', {session: false}, function(err, user, info) {
			if (err || !user) {
				console.log(err);
				res.status(400).send(info);
			} else {
				// Remove sensitive data before login
				user.password = undefined;
				user.salt = undefined;

				req.login(user, function(err) {
					if (err) {
						res.status(400).send(err);
					} else {
						res.json(user);
					}
				});
			}
		})(req, res, next);
	} else {
		/*
		 * Local
		 */
                ...
	}
};

Again, I’m using a configuration property strategy to choose the responsible backend. This is especially usefull for testing to switch strategies quickly.

Further steps

There are some further steps to execute on the profile page, if the LDAP backend is used, since you probably don’t want it to be editable. In addition you might want to disable the Sign-Up function if you use LDAP backend only.