Passport: Access req object inside serializeUser and deserializeUser functions

Created on 6 Mar 2013  Â·  9Comments  Â·  Source: jaredhanson/passport

I need to enforce that a user is only logged in at one location at a time per device type (we allow them to log in on one computer, one tablet, and one phone); simplest way to do that seems to be to just store the last session id for each type on my user object and then in deserialize I return null if the session id isn't one of the currently authorized session types. (I'd rather invalidate the old session, but the session middleware doesn't let me invalidate a session by user id)

Anyway, the problem with this is that there doesn't seem to be any way to get the req object into the deserializeUser function and thus I can't get the session id to check if the session is still valid.

Could this feature be added? Seems like it'd be simple enough to add an optional options object similar to how the strategies do it for getting the req object into the authenticate callbacks... Also, if you have an alternate suggestion, I'm certainly open to that as well =]

Most helpful comment

My first instinct with the issue I am having was also to have access to the request object, but based on all the other issues of people wanting the request object inside deserializeUser being resolved in some other fashion, maybe you have another recommendation.

With the node-orm2 library, it can be configured to work via express middleware:

app.use(orm.express("mysql://username:password@host/database", {
    define: function (db, models) {
        models.person = db.define("person", { ... });
    }
}));
app.listen(80);

app.get("/", function (req, res) {
    // req.models is a reference to models used above in define()
    req.models.person.find(...);
});

You can see here that once the database is connected and the route is hit, you have access to the models on the request object. When incorporating this into passport configuration:

passport.use({ passReqToCallback: true }, new LocalStrategy(function (request, username, password, done) {
    request.models.User
        .find({ username: username })
        .limit(1)
        .run(function (err, users) {
            var user = users[0];

            if (err) {
                done(err);
            } else if (!hasher.verify(password, user.password)) {
                done(null, false);
            } else {
                done(null, user);
            }
        });
}));

passport.serializeUser(function (user, done) {
    done(null, user.id);
});

passport.deserializeUser(function (id, done) {
    // don't have access to request object here
});

I don't have access to the request object, which has the model I need to connect to the database to deserialize the user. Suggestions?

All 9 comments

I'll give my standard reply. This is more related to authorization rather than authentication. My recommendation is to set this up as additional middleware, something like:

function enforceDeviceLogins(req, res, next) {
  if (isLoggedInOnOtherDevice(req.user)) {
    return res.send('Already logged in on another device');
  } else {
    // save device type and login somewhere
    // log the user in and serialize a session
    req.login(user, function(err) {
      if (err) { return next(err); }
      return next();
    })
  }
}

app.post('/login',
  passport.authenticate('local', { session: false }),
  enforceDeviceLogins
  function(req, res) {
    // If this function gets called, authentication was successful
    // and device checks pass.
    // `req.user` contains the authenticated user.
    res.redirect('/users/' + req.user.username);
  });

That work for you?

My first instinct with the issue I am having was also to have access to the request object, but based on all the other issues of people wanting the request object inside deserializeUser being resolved in some other fashion, maybe you have another recommendation.

With the node-orm2 library, it can be configured to work via express middleware:

app.use(orm.express("mysql://username:password@host/database", {
    define: function (db, models) {
        models.person = db.define("person", { ... });
    }
}));
app.listen(80);

app.get("/", function (req, res) {
    // req.models is a reference to models used above in define()
    req.models.person.find(...);
});

You can see here that once the database is connected and the route is hit, you have access to the models on the request object. When incorporating this into passport configuration:

passport.use({ passReqToCallback: true }, new LocalStrategy(function (request, username, password, done) {
    request.models.User
        .find({ username: username })
        .limit(1)
        .run(function (err, users) {
            var user = users[0];

            if (err) {
                done(err);
            } else if (!hasher.verify(password, user.password)) {
                done(null, false);
            } else {
                done(null, user);
            }
        });
}));

passport.serializeUser(function (user, done) {
    done(null, user.id);
});

passport.deserializeUser(function (id, done) {
    // don't have access to request object here
});

I don't have access to the request object, which has the model I need to connect to the database to deserialize the user. Suggestions?

I would make Models a global object defined by a required instead of trying
to attach it to every incoming request.

Models is a static library for returning DB objects, correct?

Sent from my iJimmy
http://twitter.com/jimmyjacobson

On Apr 13, 2013, at 12:32 PM, Eli Perelman [email protected] wrote:

My first instinct with the issue I am having was also to have access to the
request object, but based on all the other issues being resolved in some
other fashion, maybe you have another recommendation.

With the node-orm2 https://github.com/dresende/node-orm2 library, it can
be configured to work via express middleware:

var express = require('express');var orm = require('orm');var app = express();
app.use(orm.express("mysql://username:password@host/database", {
define: function (db, models) {
models.person = db.define("person", { ... });
}}));app.listen(80);
app.get("/", function (req, res) {
// req.models is a reference to models used above in define()
req.models.person.find(...);});

You can see here that once the database is connected and the route is hit,
you have access to the models on the request object. When incorporating
this into passport configuration:

passport.use({ passReqToCallback: true }, new LocalStrategy(function
(request, username, password, done) {
request.models.User
.find({ username: username })
.limit(1)
.run(function (err, users) {
var user = users[0];

        if (err) {
            done(err);
        } else if (!hasher.verify(password, user.password)) {
            done(null, false);
        } else {
            done(null, user);
        }
    });}));

passport.serializeUser(function (user, done) {
done(null, user.id);});
passport.deserializeUser(function (id, done) {
// don't have access to request object here});

I don't have access to the request object, which has the model I need to
connect to the database to deserialize the user. Suggestions?

—
Reply to this email directly or view it on
GitHubhttps://github.com/jaredhanson/passport/issues/111#issuecomment-16339252
.

The node-orm2 library automatically adds the models to every request as part of its middleware implementation. I am sure I could expose them once out of one of the initial requests:

var User;

passport.use({ passReqToCallback: true }, new LocalStrategy(function (request, username, password, done) {
    if (!User) {
        User = request.models.User;
    }

    User
        .find({ username: username })
        .limit(1)
        .run(function (err, users) {
            var user = users[0];

            if (err) {
                done(err);
            } else if (!hasher.verify(password, user.password)) {
                done(null, false);
            } else {
                done(null, user);
            }
        });
}));

passport.deserializeUser(function (id, done) {
    User.get(id, done);
});

but this seems a little hackish. Thoughts?

@eliperelman, I'm struggling with the same thing. Were you able to come up with anything better? I've only come up with something similar to what you're doing above or keeping a separate db connection with the models redefined on it.

I just want to share my solution for anybody struggling with this issue.

Please note, that this will only work as described with the new node-orm2 version that supports a third argument next for the define function (see here)

app.use(orm.express('...', {
    define: function (db, models, next) {}
});

File stack.js

require defineModels = require('./model').define;
app.use(orm.express('sqlite://sqlite3.s3dbt', { define: defineModels }));

File model.js

var async = require('async'),
    definedModels;

module.exports.define = function (db, models, next) {
    definedModels = models;
    var curriedLoad = function (file) { return function (cb) { db.load(file, cb); }; };

    async.waterfall([
            curriedLoad('.model/user'),
            curriedLoad('.model/posting'),
            // ...
    ], function (err) {
        db.models.user.hasMany(db.models.posting);
        db.sync(next);
    });
};

module.exports.model = function (name) {
    return definedModels[name];
}

You can now use the following in your passport's deserializeUser function:

var User = require('./model').model('user');

var deserializeUser = function (id, callback) {
    User.get(id, callback);
};

We are having a similar issue to this original problem, access to req object within deserializeUser. This issue seems to have gotten off-topic and is now mostly related to using the node-orm2 library.

We need access to the request object within deserializeUser because we use a REST service to deserialize the user instead of a database and the REST service needs information from the request in order to know what domain name the user requested, etc...

I was just about to fork the project for this but then found this issue, please let me know if there is a work around that you can think of, otherwise I'll just fork and submit a PR.

Addressed by merging #160. req is now optionally available in serialization/deserialization functions.

Fantastic news! Thanks @jaredhanson!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

tobymurray picture tobymurray  Â·  6Comments

Gibbo3771 picture Gibbo3771  Â·  4Comments

Manubi picture Manubi  Â·  6Comments

itaditya picture itaditya  Â·  5Comments

wongeun picture wongeun  Â·  4Comments