Search code examples
javascriptnode.jsauthenticationpassport.jspassport-local

How to store connected users with passport across server reboot?


I am playing with passport-local. Its use is very straightforward, however the way passport establishes the relation between the cookie-session-id and the connected user is still mysterious to me.

I would like to store on a database the connected sessions in order to:

  • Know the connected users
  • Detect IP changes (for security reasons)
  • Auto disconnect users not seen for a long time
  • Change the session number across each request (for security reasons)
  • Keep the users connected across server reboot (useful for debugging, and server update)

From this not-working snippet below, you can see I have two collections:

  • One to store the registered users
  • One to store the connected users

Here the snippet:

import passport from 'passport'
import LocalStrategy from 'passport-local'

import mongoose from 'mongoose'

var UserSchema = new mongoose.Schema({
  username: String,    
  password: String
})

var Sessions = new mongoose.Schema({
  session: String, // Sessions of connected users 
  username: { type: Schema.Types.ObjectId, ref: 'User' },
  ip: String, // IP of the connected user 
  lastSeen: Date, // When the user made his last request 
})

var User = mongoose.model('UserSchema')

How should I proceed to populate the Session table with passport?


Solution

  • Well, I think I understood and I should be able to answer my own question.

    Passport is not guilty

    Passport is not responsible for persistence, but express.session is. Passport adds a wrapper in the middleware to intercept HTTP requests and populate the request with methods and attributes such as LogOut or username.

    By default express.session stores data into memory so this data is destroy when the server is stopped. Passport stores only one field into the session:

    { passport: { user: 'user-id' }}
    

    Now we exonerated Passport, we can focus on the sessions.

    Sessions

    Express session automatically creates a cookie with a signed sid made from the secret:

    app.use(express.session({
    secret: 'keyboard cat' })

    This sid is the cookie id and it is used by express.session to link session data (stored on the server only), with the request made from the client.

    Persistent session

    To achieve what you wanna do (... What I would like to do), you have to make the session persistent. If you which to use MongoDB, you can use connect-mongo which store session data into a Mongo collection:

    const MongoStore = require('connect-mongo')(express.session);
    

    var app = express();

    app.configure(function() {
      app.use(express.session({     
        secret: 'keyboard cat', 
        rolling: true,
        store: new MongoStore({ url: 'mongodb://localhost/db', stringify: false })
      }));
      //...
    }
    

    When you start your server and someone make a request, it will add an entry into the db collection:

    { 
        "_id" : "AcBXmfIXvz_y2Jp4nw1zfJvZTVPsCq7D", 
        "session" : {
            "cookie" : {
                "originalMaxAge" : null, 
                "expires" : null, 
                "secure" : null, 
                "httpOnly" : true, 
                "domain" : null, 
                "path" : "/"
            }, 
            "passport" : {
                "user" : NumberInt(1)
            }
        }, 
        "expires" : ISODate("2018-06-03T18:10:54.237+0000")
    }
    

    You can see that Passport only store the user id as explained.

    In summary

    Know the connected users

    Now, you have this information in MongoDB. However when a user disconnects, the data session will remains in the database. You may use the compatibility mode:

    app.use(session({
        store: new MongoStore({
          url: 'mongodb://localhost/test-app',
          autoRemove: 'interval',
          autoRemoveInterval: 10 // In minutes. Default
        })
    }));
    

    To automatically remove expired session from the database.

    Detect IP changes (for security reasons)

    You have to store this information into your session with:

    req.session.ip = (req.headers['x-forwarded-for'] ||
         req.connection.remoteAddress ||
         req.socket.remoteAddress ||
         req.connection.socket.remoteAddress).split(",")[0];
    

    Auto disconnect users not seen for a long time

    You have to implement that manually. You can use an event from MongoStore such as touch triggered when a session has been touched.

    Change the session number across each request (for security reasons)

    This is more complicated and I don't have the answer yet. Changing the cookie sid across connections means to regenerate a new session id and update the MongoDB. I don't have a working solution yet.

    Keep the users connected across server reboot (useful for debugging, and server update)

    Well, it works thanks to connect-mongo