Search code examples
node.jsexpressexpress-session

Session auto logout after inactivity


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
  }
}))

Solution

  • 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
      }
    })();