Ok, so Passport is really flexible here and accommodates exactly what I needed. Just need to first get up to speed with how it has been implemented internally. Here, I shall call the solution "Parasitic Session Polymorphism" because of the way it uses the existing session, but injects just enough new information to appear to be a different user type etc at runtime. So the solution is basically (with custom code that would need to be swapped out if anyone wishes to use the basic idea):
passport.serializeUser(function (user, done) {
if (user) {
var identityStack = user.identityStack || [];
var parentId = user.parentId || '';
var userStorage = {
_id: user._id,
identityStack: identityStack,
entityId: user.entityId,
entityType: user.entityType,
roles: user.roles,
parentId: parentId
};
done(null, userStorage);
}
});
passport.deserializeUser(function (userStorage, done) {
var id = userStorage._id;
User.findOne({_id: id}).exec(function (err, user) {
if (user) {
if(userStorage.identityStack && userStorage.identityStack.length > 0) {
user.identityStack = userStorage.identityStack;
user.entityId = userStorage.entityId;
user.entityType = userStorage.entityType;
user.roles = userStorage.roles;
user.parentId = userStorage.parentId
}
return done(null, user);
} else {
return done(null, false);
}
});
});
Then in order to actually call the passport.serializeUser from within your own code, you can achieve the required result by currying the http req object. For my implementation I push and pop the added information off a javascript array associated with .prototype as an extension to the Mongoose User Model:
PUSH KEY CODE:
var memento = {
entityId : agency.id,
entityType: 'user',
roles: ['admin', 'user']
};
var user = req.user;
user.pushIdentity(memento);
var doneWrapper = function (req) {
var done = function (err, user) {
if(err) {
console.log('Error occurred serializing user');
return;
}
req._passport.session.user = user;
console.log('user serialized successfully');
return;
};
return done;
};
req._passport.instance.serializeUser(user, doneWrapper(req));
return res.send({success: true, user: req.user});
and POP KEY CODE:
var user = req.user;
user.popIdentity();
var doneWrapper = function (req) {
var done = function (err, user) {
if(err) {
console.log('Error occurred serializing user');
return;
}
req._passport.session.user = user;
console.log('user serialized successfully');
return;
};
return done;
};
req._passport.instance.serializeUser(user, doneWrapper(req));
return res.send({success: true, user: user});
Clearly the code is not yet production ready, move out the console.log statements to a logging strategy and so on but that is the basic concept. The signature to the serializeUser method is defined in the Passport github code base under lib/authenticator.js.
KEY PART:
Authenticator.prototype.serializeUser = function(fn, req, done) {
if (typeof fn === 'function') {
return this._serializers.push(fn);
}
// private implementation that traverses the chain of serializers, attempting
// to serialize a user
var user = fn;
// For backwards compatibility
if (typeof req === 'function') {
done = req;
= undefined;
}
Thanks to Biba for his contributions, really appreciated and assisted in getting to a solution.