JO3-W3B-D3V
JO3-W3B-D3V

Reputation: 2134

SSO with NodeJS on Linux Server in Windows domain

FYI

I know that people have asked similar questions, such as this, however I've not found an answer on here, now if there is an answer to this problem, by all means correct me & I'll delete my question, or whatever! 🙂


Potential Options

From what I've read up online, a potential would be to use something like kerberos and/or spenago, keep in mind I'm no expert on these tools/subject matters. But I was wondering if there was any other potential options? - I know if I do go down the route of kerberos, I have no doubt that it'll require some tinkering, maybe some trial & error, I'm not sure, currently I'm still looking at what my options are.

If you guys think that the kerberos route may be my best bet, then could you guys point me in the direction of some example(s) on how I can get up & running?

In the ideal world I'd be able to just use some NPM package that just allows me to do this out of the box, but I've also found nothing that could allow me to do this.

Upvotes: 2

Views: 2009

Answers (1)

JO3-W3B-D3V
JO3-W3B-D3V

Reputation: 2134

So, after doing much research with one guy that I work with, we finally found a solution that seems to work for us. I won't go into too much detail here, if you want to read more into it, then you can find that I've started to write up more about it on my own website, here. 🙂

I'm not doing that to be lazy, but due to the fact that if I tried to cram everything into this answer, then I feel that it would be like reading an essay.

Dependencies

$ npm i --save express kerberos activedirectory

Code

// config.js

const config = {
  url: "ldap://dc.example.com",
  baseDN: "dc=example,dc=come",
  username: "[email protected]",
  password: "P@55W0rd!231",
  group: "Admin",
  service: "HTTP",
};

module.exports = config;
// isMemberOf .js

/**
 * This function will be used to check that a user is
 * a member of a given active directory group, a typical
 * example may include 'Admin', 'Super User', etc.
 * This will return a promise to allow the consuming
 * code base to take advantage of async/await syntax,
 * making the code more readable.
 *
 * @param {String} username
 * @param {String} group
 * @param {ActiveDirectory} activeDirectory
 * @return {Promise}
 */
const isMemberOf = (username, group, activeDirectory) => {
  const promise = new Promise((resolve, reject) => {
    activeDirectory.isUserMemberOf(username, group, (err, isMember) => {
      if (err) {
        reject(err);
      } else if (!isMember) {
        reject(username + " is not a member of " + group);
      } else {
        resolve(isMember);
      }
    });
  });

  return promise;
};

module.exports = isMemberOf;
// userExists.js

/**
 * This function will just be used to check that a user
 * within an active directory domain. This will return
 * a promise to allow the consuming code base to take
 * advantage of async/await syntax, making the
 * code more readable.
 *
 * @param {string} username
 * @param {ActiveDirectory} activeDirectory
 * @returns {Promise}
 */
const userExists = (username, activeDirectory) => {
  const promise = new Promise((resolve, reject) => {
    activeDirectory.userExists(username, (err, exists) => {
      if (err) {
        reject(err);
      } else if (!exists) {
        reject("User does not exist");
      } else {
        resolve(username);
      }
    });
  });

  return promise;
};

module.exports = userExists;
// sso.js

const config = require("./config");
const kerberos = require("kerberos");
const ActiveDirectory = require("activedirectory");
const userExists = require("./userExists");
const isMemberOf = require("./isMemberOf");

/**
 * This method will be used to indicate that there was a problem
 * with starting up the server.
 *
 * @param {Response} res
 * @param {Error} err
 */
const forbidden = (res, err) => {
  console.error("Something failed: " + err);
  res.status(403).send();
};

/**
 * This is some single sign on middleware that will authenticate
 * a user via their SPNEGO & check to see if they're within a
 * specific group.
 */
const sso = async (req, res, next) => {
  const { authorization } = req.headers;
  const { group } = config;

  // If not authorization header exists, return with WWWW-Authenticate Negotiate.
  // This will get the browser to automatically respond with a SPNEGO ticket, if the
  // browser is chrome then the service may need to be whitelisted, as it won't return
  // a SPNEGO ticket by default.
  if (!authorization) {
    res.set("WWW-Authenticate", "Negotiate");
    return res.status(401).send();
  }

  // Get our SPNEGO ticket
  const ticket = authorization.substring(10);
  let server;

  // This try catch block will just start start the server side
  // kerberos authentication context.
  try {
    server = await kerberos.initializeServer(config.service);
  } catch (err) {
    return forbidden(res, err);
  }

  // This try catch block will just perform the authentication
  // using the snego ticket & the configured kerberos keytab.
  try {
    await server.step(ticket);
  } catch (err) {
    return forbidden(res, err);
  }

  const { username } = server;
  const activeDirectory = new ActiveDirectory(config);

  // This try catch block will simply check that the user
  // exists within the AD domain.
  try {
    await userExists(username, activeDirectory);
  } catch (err) {
    return forbidden(res, err);
  }

  // This try catch block will simply check that the user
  // is a member of a specific AD group, i.e. 'Admin',
  // 'Super User', etc.
  try {
    await isMemberOf(username, group, activeDirectory);
  } catch (err) {
    return forbidden(res, err);
  }

  // As we know that the user has been authenticated & is a member of the given
  // active directory group, we can safely proceed through to
  // execute any further business logic.
  next();
};

module.exports = sso;
// index.js

const express = require("express");
const app = express();
const sso = require("./sso");

app.use(sso);
app.get("/", (req, res) => {
  res.status(200).send("SSO Test - You're Logged In! :)");
});

app.listen(8080);

Upvotes: 3

Related Questions