10

Is there a built in feature in express session, to enable auto logout after given time of inactivity ? I am using it as below, and want it to logout if session is inactive for half an hour.

app.use(session({
  key: 'sessid',
  secret: 'This is secret',
  resave: true,
  saveUninitialized: true,
  store: new RedisStore(redisOptions),
  cookie: {
    path: '/',
    httpOnly: true,
    secure: false,
    maxAge: 24 * 60 * 60 * 1000,
    signed: false
  }
}))
Seth
  • 10,198
  • 10
  • 45
  • 68
nishant pathak
  • 342
  • 1
  • 4
  • 17
  • 2
    I think rolling session https://github.com/expressjs/session#rolling may solve the purpose. It renews the session everytime on server response. You can set max age to 30 minutes. When there is no activity max age will destroy the session. While when there is any activity, rolling will renew the session to set for next 30 minutes. – Ritesh Kumar Gupta Jul 01 '16 at 11:23
  • I don't think there is a built in feature in express for this. You need to monitor the user on the [client side](http://stackoverflow.com/questions/667555/detecting-idle-time-in-javascript-elegantly) – demux Jul 01 '16 at 11:23
  • @ritesh_NITW This is interesting. This would not really work though unless every user action created a request. – demux Jul 01 '16 at 11:25
  • Either way, you are providing 24 hours and not 30 minutes. You need `24 * 60 * 1000` for 1800000 milliseconds or 30 minutes. – Seth Jul 01 '16 at 11:29
  • 1
    @Seth I think the cookie should not expire. The cookie expiring will disregard any user activity and will result in bad user experience. – demux Jul 01 '16 at 11:32
  • 1
    If the front end is separate from the server, you could have client side routing middleware that checks the cookie and visually logs you out, thus proving a good UX. But on a traditional postback app, you're right. – Seth Jul 01 '16 at 11:43
  • @demux It depends on what the app is used for, it is not totally a bad experience – Fillipo Sniper Aug 18 '18 at 00:32

2 Answers2

12

Ok, I'll throw my two cents into the ring here.

Even though it's in theory possible to implement this using rolling session, I don't think you should...

  • It would require each user action the send a request to the server, in order for the user not to be logged out.
  • You miss an opportunity to inform your user that he/she will be logged out automatically soon (this is what the banks do, for example).
    @Seth did point out in a comment above that there is actually a way to remedy this: "If the front end is separate from the server, you could have client side routing middleware that checks the cookie and visually logs you out, thus proving a good UX."
    I think this is clever, but I also think it's like putting lipstick on a pig.

I believe that the best approach here is to handle this on the client side.

I would suggest something like this:

var AutoLogout = (function() {
  function AutoLogout() {
    this.events = ['load', 'mousemove', 'mousedown',
                   'click', 'scroll', 'keypress'];

    this.warn = this.warn.bind(this);
    this.logout = this.logout.bind(this);
    this.resetTimeout = this.resetTimeout.bind(this);

    var self = this;
    this.events.forEach(function(event) {
      window.addEventListener(event, self.resetTimeout);
    });

    this.setTimeout();
  }

  var _p = AutoLogout.prototype;

  _p.clearTimeout = function() {
    if(this.warnTimeout)
      clearTimeout(this.warnTimeout);

    if(this.logoutTimeout)
      clearTimeout(this.logoutTimeout);
  };

  _p.setTimeout = function() {
    this.warnTimeout = setTimeout(this.warn, 29 * 60 * 1000);

    this.logoutTimeout = setTimeout(this.logout, 30 * 60 * 1000);
  };

  _p.resetTimeout = function() {
    this.clearTimeout();
    this.setTimeout();
  };

  _p.warn = function() {
    alert('You will be logged out automatically in 1 minute.');
  };

  _p.logout = function() {
    // Send a logout request to the API
    console.log('Sending a logout request to the API...');

    this.destroy();  // Cleanup
  };

  _p.destroy = function() {
    this.clearTimeout();

    var self = this;
    this.forEach(function(event) {
      window.removeEventListener(event, self.resetTimeout);
    });
  };

  return AutoLogout;
})();

es2015

class AutoLogout {
  constructor() {
    this.events = ['load', 'mousemove', 'mousedown',
                   'click', 'scroll', 'keypress'];

    this.warn = this.warn.bind(this);
    this.logout = this.logout.bind(this);
    this.resetTimeout = this.resetTimeout.bind(this);

    this.events.forEach((event) => {
      window.addEventListener(event, this.resetTimeout);
    });

    this.setTimeout();
  }

  clearTimeout() {
    if(this.warnTimeout)
      clearTimeout(this.warnTimeout);

    if(this.logoutTimeout)
      clearTimeout(this.logoutTimeout);
  }

  setTimeout() {
    this.warnTimeout = setTimeout(this.warn, 29 * 60 * 1000);

    this.logoutTimeout = setTimeout(this.logout, 30 * 60 * 1000);
  }

  resetTimeout() {
    this.clearTimeout();
    this.setTimeout();
  }

  warn() {
    alert('You will be logged out automatically in 1 minute.');
  }

  logout() {
    // Send a logout request to the API
    console.log('Sending a logout request to the API...');

    this.destroy();  // Cleanup
  }

  destroy() {
    this.clearTimeout();

    this.events.forEach((event) => {
      window.removeEventListener(event, this.resetTimeout);
    });
  }
}

Partial polling solution:

var activityPolling = (function() {
  var events = ['load', 'mousemove', 'mousedown', 'click', 'scroll', 'keypress'];
  var active = true;
  var timeout;

  function poll() {
    if(active) {
      console.log('polling the server...')
    }
  }

  function setIdle() {
    active = false;
  }

  function setActive() {
    active = true;
    if(timeout)
      clearTimeout(timeout);
    timeout = setTimeout(setIdle, 30 * 60 * 1000);
  }

  function destroy() {
    clearInterval(interval);

    events.forEach(function(event) {
      window.removeEventListener(event, setActive);
    });
  }

  events.forEach(function(event) {
    window.addEventListener(event, setActive);
  });

  setActive();

  var interval = setInterval(poll, 60 * 1000);

  return {
    interval: interval,
    destroy: destroy
  }
})();
demux
  • 4,544
  • 2
  • 32
  • 56
  • Using the `idle` seems great! However, how would you actually request a logout from the app? Hit the logout endpoint within the callback? FWIW, my suggestion is a widely used method in lots of single page applications as you must always ensure, as you pointed out, that your UX doesn't generate a sense of nothing happing due to an expired cookie / token that's not being handled by the client. I've worked on redux / angular / marionette projects which used the authorization middleware that I suggested and it provided a nice UX and ensured you weren't making unauthorized requests without knowing. – Seth Jul 01 '16 at 12:03
  • @Seth, I did not know that this was widely used. Yes, I would suggest sending a logout request to the `API`. But maybe the best solution is somewhere in between, i.e. poll the server on a 1 minute interval as long as the user is active. – demux Jul 01 '16 at 12:10
  • 1
    That makes sense... good idea. It's probably something that needs to be catered to the context of the OP's project. Also in the right context, polling might be a viable solution. In other scenarios it may prove to be too much unneeded chatter. It's tough to say without knowing more. – Seth Jul 01 '16 at 12:16
  • Thanks for the response, @demux. I prefer non polling solution as it does not send unnecessary traffic to the server. Your client based approach looks much better to me. At server we are not maintaining any state. If client still continues after maxAge time, it is going to logout and session will be destroy on server. How to prevent that on server ? With Rolling session, I fell in every request it will update the session table in session store which also not the best way. Is there any preferred solution ? – nishant pathak Jul 01 '16 at 15:21
  • You can make the session last forever on the server. You can also have the user automatically logged out on closing the window: `window.addEventListener('unload', this.logout)` – demux Jul 01 '16 at 16:26
  • 1
    @demux Does this javascript have to be imported on every page of the application? – Ger Jan 28 '19 at 18:17
  • @EdeGerSil, these are just some concepts, and I can't say for sure how they would fit into your system. However, the simple answer to your question is probably "yes". Today we generally bundle all our javascript using babel and then the bundle is imported in the base template of your web -site or -application. There is also the fact that many web applications today are SPA (single page applications). – demux Feb 02 '19 at 00:20
2

Rolling session may solve the purpose.

If you use the "rolling" option for session to "true," it will update the session timeout on new requests.

What you can do is: set max-age to 5 minutes.

 maxAge: 30*10000

When there is no activity max-age will destroy the session. However, when there is any activity, rolling will renew the session to be alive for next 30 minutes.

Again, the word in-activity in this question is little misleading. In-activity could be any (or all) of no-mouse-movement, no-mouse-click, or no-interaction-with-server.
If you refer inactivity as no-interaction-with-server, this logic will work. However for no-ui-interactions inactivity, you need to handle from client side

Noam M
  • 3,156
  • 5
  • 26
  • 41
Ritesh Kumar Gupta
  • 5,055
  • 7
  • 45
  • 71
  • Rolling Session, As far as my understanding, in every request - response cycle, updates the maxage and restores it into the db. Will it not have unnecessary db calls ? – nishant pathak Jul 01 '16 at 15:43