1

Keycloak is an open-source authentication and identity management solution written in Java. It provides a nodejs adapter using which I am able to successfully integrate with express. Here is the routes file which works:

    'use strict';

module.exports = function(app) {
    var Keycloak = require('keycloak-connect');
    var session = require('express-session');


    var memoryStore = new session.MemoryStore();

    app.use(session({
          secret: 'mySecret',
          resave: false,
          saveUninitialized: true,
          store: memoryStore
    }));

    var keycloak = new Keycloak({
          store: memoryStore
    });
    app.use(keycloak.middleware({
          logout: '/logout',
          admin: '/'
    }));


    // var lRController = require('../controllers/LRController');
    //
    // app.route('/lrs').get(lRController.list_all_lrs).post(lRController.create_a_lr);


    var DeliveryOrderController = require('../controllers/DeliveryOrderController');
    app.route('/').get(keycloak.protect(), DeliveryOrderController.getAllDos)
    app.route('/api/dos').get(keycloak.protect(), DeliveryOrderController.getAllDos).post(DeliveryOrderController.createDo);
    app.route('/api/do').put(DeliveryOrderController.updateDo);
    app.route('/api/do/:doNumber').get(DeliveryOrderController.getDoByDoNumber);
    app.route('/api/do/location/:locationId').get(DeliveryOrderController.getDoByLocation);
    app.route('/api/do/branch/:branchId').get(DeliveryOrderController.getDoByBranch);
    app.route('/api/do').delete(DeliveryOrderController.deleteDo);


    var TransportDeliveryOrderController = require('../controllers/TransportDeliveryOrderController');

    app.route('/api/tdos').get(TransportDeliveryOrderController.getAllTdos).post(TransportDeliveryOrderController.createTdo);
    app.route('/api/tdo').put(TransportDeliveryOrderController.updateTdo);
    app.route('/api/tdo/:tdoNumber').get(TransportDeliveryOrderController.getTdoByTdoNumber);
    app.route('/api/tdo/status/:status').get(TransportDeliveryOrderController.getTdoByStatus);
    app.route('/api/tdo/status/:status/do/:doNumber').get(TransportDeliveryOrderController.getTdoByStatusAndDo);

};

As you can see in the Delivery order routes, I have two routes(copies of the same route) protected by keycloak.protect(). I am trying to do the same in sails. I have the following questions for doing that.

a. To integrate keycloak into express the following things are done to protect the routes

  1. Require Keycloak and express session:

    var Keycloak = require('keycloak-connect'); var session = require('express-session');

  2. Define a memory store for the storing the sessions:

    var memoryStore = new session.MemoryStore();

  3. Include the session as middleware in express

    app.use(session({ secret: 'mySecret', resave: false, saveUninitialized: true, store: memoryStore }));

  4. Initiate Keycloak:

    var keycloak = new Keycloak({ store: memoryStore });

  5. Include keycloak Middleware into express middleware:

    app.use(keycloak.middleware({ logout: '/logout', admin: '/' }));

  6. Protect the route using keycloak.protect()

    app.route('/api/dos').get(keycloak.protect(),DeliveryOrderController.getAllDos).post(DeliveryOrderController.createDo);

I need to establish similar steps in sails. How do I do these things in sails?

I am assuming http.js is where I add middleware. If I do, how do access keycloak in routes.js to use keycloak.protect().

For instance I can add the protect function in the following manner:

'/foo': [
 keycloak.protect(),
  { controller: 'user', action: 'find' }
]

Here is the nodejs adapter for keycloak - https://github.com/keycloak/keycloak-nodejs-connect

2 Answers2

3

I finally found an answer for this. The problem is that keycloak.middleware() returns a list of functions and app.use() is happy with that. Sails takes the http.middleware list and adds to it and calls app.use on the resulting list. If you just include the keycloak.middleware() you have a list of functions which includes an array of functions. Express ignores the array since it is not a function.

You need to expand the list into separate functions. Create a keycloak object at the top of http and initialize it. Then put this at the bottom of the config/http.js file:

function expandList() {
    var newOrder = [];
    for (let i in module.exports.http.middleware.order)
    {
        var label = module.exports.http.middleware.order[i];
        var functor = module.exports.http.middleware[label];
        if (functor && functor.constructor === Array) {
            for (let j in functor) {
                let newlabel = label + "." + j;
                newOrder.push(newlabel);
                module.exports.http.middleware[newlabel] = functor[j];
            }
        } else {
            newOrder.push(label);
        }
    };
    module.exports.http.middleware.order = newOrder;
    return "";
}
var result = init();

Inside the http.middleware object you need to use: keycloakMiddleware: keycloak.middleware(), and add it to order array.

Also add a policy to call protect and include this:

var kc = sails.config.http.keycloak.protect();
return kc(req, resp, next);

Hope this helps if you still need to solve this.

Maybe Sails should accept an array and expand it before calling Express

nswartz
  • 51
  • 5
  • One more note: You could have a function that iterates over the middleware list and have Sails call that single function. That could be included into keycloak-connect lib. – nswartz Jan 17 '18 at 15:10
  • How to use keycloak.enforcer in sails? Where does it has to be used? Any examples ? Thank you – Sindhu Arju Feb 17 '19 at 19:09
2

The above answer does not work for Sails 1.0. It now requires that the middleware be a function, not an array and keycloak returns an array from keycloak.middleware. Here is what seems to work: Create a service: KeycloakService

var session = require('express-session');
var Keycloak = require('keycloak-connect');

var memoryStore = new session.MemoryStore();
var KeycloakConfig = {
  "realm": "somerealm,
  "auth-server-url" : "https://server.com/auth",
  "resource" : "someresource,
};

module.exports = {
  config: KeycloakConfig,
  setConfig: function (config) {
   return new Keycloak({ store: memoryStore }, config);
  }
}

Now in http.js put the following at the top

var KeycloakService = require('../api/services/KeycloakService');
var masterKeycloak = setupKeycloak();
var kcMiddleware = masterKeycloak.middleware();

function setupKeycloak() {
 if (KeycloakService.keycloak == null) {
  var config = Object.assign({}, KeycloakService.config);
  config.bearerOnly = true;
  KeycloakService.keycloak = KeycloakService.setConfig(config);
 }
 return KeycloakService.keycloak;
}

function recurseCallFunction(arr, i, req, res, next) {
  if (arr.length > i) {
    arr[i](req, res, () => recurseCallFunction(arr, i+1, req, res, next));
  } else {
    next();
  }
}

Then in the middleware.order array include "keycloakMiddleware" and below the order array use

'keycloakMiddleware': function (req, res, next) {recurseCallFunction(kcMiddleware, 0, req, res, next);}
You will also need to define sails.config.http.keycloak: masterKeycloak

This will provide a function that recursively calls the middles functions in Keycloak. You will need a policy defined. That policy can do:

  var keycloak = sails.config.http.keycloak;
  if (!req.hostname) {
    req.hostname = req.host;
  }
  var authWithKeycloak = keycloak.protect();
  if (authWithKeycloak) {
 authWithKeycloak(req, res, next);
  } else {
 sails.log.warn('Keycloak authentication could not obtain a protection checker. Contact Developers');
  }

This technique should help with Keycloak Policy Enforcer. You can add to the config to enable those and use them per the documentation with the keycloak.protect. I do not know anything about enforcers so I cannot help further on that.

nswartz
  • 51
  • 5