Loopback: Sliding expiration of access token

Created on 14 May 2014  路  33Comments  路  Source: strongloop/loopback

It should be possible to configure LoopBack application in such way that the access token expires after a certain time of inactivity. The current implementation supports only the expiration after a fixed time since login.

See strongloop/loopback-angular#39 for a possible solution:

// ...
app.use(loopback.token({/* config */});
app.use(function(req, res, next) {
  var token = req.accessToken;
  if (!token) return next();

  var now = new Date();

  // performance optimization:
  // do not update the token more often than once per second
  if (now.getTime() - token.created.getTime() < 1000) return;

  // update the token and save the changes
  req.accessToken.created = now;
  req.accessToken.ttl = 60; /* session timeout in seconds */
  req.accessToken.save(next);
});
// register other middleware, etc.
feature good first issue help wanted

Most helpful comment

@mbuster sure, I've used a combination of tib's and av-k's code above. In server.js:

boot(app, __dirname, function(err) {
  if (err) throw err;
  if (require.main === module) {  // if we are running loopback as our server
    app.use(loopback.token({}));
    app.use(function updateToken(req, res, next) {
      var token = req.accessToken; // get the accessToken from the request
      if (!token) return next(); // if there's no token we use next() to delegate handling back to loopback
      var now = new Date();
      // EDIT: to make sure we don't use a token that's already expired, we don't update it
      // this line is not really needed, because the framework will catch invalid tokens already
      if (token.created.getTime() + (token.ttl * 1000) < now.getTime()) return next();
      // performance optimization, we do not update the token more often than once per five seconds
      if (now.getTime() - token.created.getTime() < 5000) return next();
      token.updateAttribute('created', now, next); // save to db and move on
    });
    app.start();
  }
});

Also, in models-config.js I have configured the ttl in the custom user model I'm using like so:

    "options": {
      "emailVerificationRequired": false,
      "ttl": 28800
    }

That way loopback enforces the ttl value.

All 33 comments

I am closing this issue, since no user was asking for this enhancement and it clearly was not a priority for us so far. We can reopen it again later when there is more demand.

Sorry I did not see this before. I think this is very useful. Especially for mobile apps. Please reopen. :+1:

@meng-zhang I am afraid we don't have bandwidth to implement this anytime soon. Would you mind submitting a pull request?

  • add a configuration option for enabling the sliding expiration, probably via AccessToken.settings.slidingExpiration
  • implement sliding expiration in loopback.token middleware

I didn't see any reference of this in the docs, so I assume it still hasn't been implemented?

If so I'd like to take a stab at it.

@diegonetto It's not implemented. Contribution is welcome!

@diegonetto I would definitely use this as well

Perhaps the onus could be on the client-side? Generate a new accessToken using the current accessToken?

@bajtos I think you should call next() every time before you return. :)
This is how it should look like if you want to keep alive the token for a week.

server.js

app.use(loopback.token());
app.use(function(req, res, next) {
    var token = req.accessToken;
    if (!token) {
        return next();
    }
    var now = new Date();
    if ( now.getTime() - token.created.getTime() < 1000 ) {
        return next();
    }
    req.accessToken.created = now;
    req.accessToken.ttl     = 604800; //one week
    req.accessToken.save(next);
});
...
app.start...

@tib thanks a lot!

@bajtos Can this issue be reopened? I wouldn't mind giving it a shot.

@seanmangar Feel free to submit a PR and link to this issue. We can reopen it at that time.

It would be really useful !

+1

This would be very handy. We have a need to have an inactivity be the cause of logout, as well as the database access for this would be slow, hooking to memcache or redis would be way faster.

This would be super-useful :)

Do we have any update on this?

+1 This feature would be nice to have--configurable from config.json or what have you...

+1 Basing expiration on inactivity is a must for most apps.

Hello, the status of this feature request remains the same - it's not a priority for us right now. However, we welcome community contributions and if you decide to implement this feature, then I am happy to help you along the way. Just open a pull request and mention my handle (@bajtos).

Prolongation by each request (maximum one update per day)

  /* Access token live time prolongation */
  app.use(function accessTokenProlongation(req, res, next) {
    var token = req.accessToken,
        now = new Date(),
        createTS = token ? token.created.getTime() : 0,
        expiredTS = token ? req.accessToken.ttl * 1000 : 0,
        dayTS = 24 * 60 * 60 * 1000;

    if (!token || (token && (createTS + expiredTS < now))) {
      return next();
    }

    if (now.getTime() - createTS < dayTS) {
      return next();
    }

    token.updateAttribute('created', now, next);
  });

@bajtos I'm trying to figure out where to implement this, because it's really a thing lb needs imho. I'm currently thinking about putting the middleware bit in loopback/common/models/access-token.js, in the AccessToken.prototype.validate function. Also would love to know how/where to add a configuration option for enabling the sliding expiration, you mentioned doing it via AccessToken.settings.slidingExpiration but I have no clue where that is (which file).

Hmm, reminds me I still need to implement this too! I'll just be doing it as @av-k showed I think.

@buokae hello, I think this is the right place where to put the new code: https://github.com/strongloop/loopback/blob/0f40ca8f8eb1ed50cc588ae72893ce2ca33d80a2/server/middleware/token.js#L140-L148

I am not sure if it's a good idea to put the code extending expiration time into AccessToken#validate(), because that function can be called from other code outside of the middleware too?

Also would love to know how/where to add a configuration option for enabling the sliding expiration, you mentioned doing it via AccessToken.settings.slidingExpiration but I have no clue where that is (which file).

Model settings can be configured in model .json file or altered in model-config.json:

Configuring the setting via model file (common/models/my-access-token.json):

{
  "name": "MyAccessToken",
  "base": "AccessToken",
  "slidingExpiration": true,
  "properties": {
  },
  "methods": {
  }
}

See https://loopback.io/doc/en/lb3/Authentication-authorization-and-permissions.html#customizing-the-accesstoken-model for instructions on what else needs to be changed when using custom AccessToken model.

Changing the settings via server/model-config.json:

{
  "AccessToken": {
    "dataSource": "db",
    "public": false,
    "options": {
      "slidingExpiration": true
    }
}

I'm agree with the solution. Perhaps a new "refreshToken" public method in User model could be another solution to keep login from frontend.

@bajtos Thank you for the explanation. I'm sorry but right now I don't have time to make that pr. I hope to do it soon but I've only been using lb for two months now so I'm still getting to know the framework really, and I guess it'll take a while to get acquainted with the code base. If anyone else wants to take a go at it, please do so.

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@buokae are you still using loopback? Do you plan on working on this at all?

@nVitius yes I am still using loopback, and no I don't plan on working on this in the near future due to time constraints, and lack of motivation to work on lb3. I'd love to make this but since the whole lb team seems to be concentrating on lb next (lb4) I don't really feel the motivation to create this pr on the lb3 repo when it is probably going to be obsolete within a year. It takes quite a lot of time to learn the codebase and that's just going to be time wasted as far as I'm concerned. Imho the aggressive version schedule lb is following is hurting development. Plus, I have a solution in my own code that works fine for me.

@buokae, would you be interested in sharing your solution. We are finding this be a requirement as well, and I'm very interested in seeing how others have approached it.

@mbuster sure, I've used a combination of tib's and av-k's code above. In server.js:

boot(app, __dirname, function(err) {
  if (err) throw err;
  if (require.main === module) {  // if we are running loopback as our server
    app.use(loopback.token({}));
    app.use(function updateToken(req, res, next) {
      var token = req.accessToken; // get the accessToken from the request
      if (!token) return next(); // if there's no token we use next() to delegate handling back to loopback
      var now = new Date();
      // EDIT: to make sure we don't use a token that's already expired, we don't update it
      // this line is not really needed, because the framework will catch invalid tokens already
      if (token.created.getTime() + (token.ttl * 1000) < now.getTime()) return next();
      // performance optimization, we do not update the token more often than once per five seconds
      if (now.getTime() - token.created.getTime() < 5000) return next();
      token.updateAttribute('created', now, next); // save to db and move on
    });
    app.start();
  }
});

Also, in models-config.js I have configured the ttl in the custom user model I'm using like so:

    "options": {
      "emailVerificationRequired": false,
      "ttl": 28800
    }

That way loopback enforces the ttl value.

@buokae very slick, thanks for sharing.

Thank you @buokae !
Your snippet helped me solve our (related) problem as well :)

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

Is this feature already implemented in the newest version?

Is this feature already implemented in the newest version?

Not AFAIK.

LoopBack 3 is in LTS mode and not accepting new features any more, see https://loopback.io/doc/en/contrib/Long-term-support.html

I am closing this issue.

Was this page helpful?
0 / 5 - 0 ratings