Just a question:
So as JWTs are stateless and don't have any refresh mechanism, how do you guys handle JWT expirations? Having JWTs with a 1d expiration can be a real pain for the users as they would need to log in again every day, not to mention tokens with a 15m exp, which is, by the way, a recommended way to use them.
Any thoughts?
I was thinking a little bit about this myself, just haven't gotten to the point where I need to implement it yet.
Ideally I want something similar to what I experience with Google products. Your session on the same device seems to be active for ~30 days with heavy usage, or if you don't use the product several days in a row, the session expires sooner.
So, let's say, every time the user interacts with the app, the "session" is extended by 5 extra days, but only up to 30 days in total at which point we should ask them for login details again. Of course, keep in mind Google takes other precautions like 2FA and geo fencing, which arguably makes this long session thing safer. Having said that, someone like GitHub - never seems to kill your session, they only ask for confirmation password in security sensitive areas of the app.
Here is one idea on how to achieve this with feathers and jwt (conceptually, not .. code wise):
exp) to 5 days and a special key firstIssuedAtfirstIssuedAt value but refreshes exp to 5 daysonce the server sees firstIssedAt is >30 days, it rejects this token and the user has to reauthenticate
this way if the user is not active, the token will expire in 5 days and the user will need to reauthenticate
Not sure this works, I'll think more about it. Another option is to store last activity per user/token in the DB and track time that way.
Similarly, I'm also considering rejecting tokens if the user's password change time is newer than the token's issued at timestamp. This means if the user changes the password it invalidates all the tokens. Doesn't apply when using third party oauth2 as authentication.
And regarding the 15m, 1d, 5d thing..
It doesn't matter (does it?) if you store an opaque session id or a jwt token in the cookie/local storage. In both cases they are a secret and will give access to whomever owns them. And so if you want a session to last for 5 days, you'll have to make that opaque session id be valid for 5 days. Same with jwts.
The only consideration are things like invalidating sessions when user's change the password (good practice) or possibly if they logout (on any device). In those cases, with sessions, you can just clear the session. But with jwt you need to do a different kind of lookup to the user store.
But! Not a security expert. Would love to hear more about how to get the best of security and UX when it comes to this stuff..
@KidkArolis Thanks for your reflexion on the topic!
According to the JWT lifecycle, there's a SO answer from an Auth0 employee that does propose something similar. I was thinking about this way too. Unfortunately, when it comes to security - just like you've said - it's a good practice to be able to invalidate sessions when something suspicious happens. But if you want to have stateless authentication, this is impossible, as you'd have to store information about the tokens somewhere anyways - which of course breaks the statelessness of JWTs and takes some benefits that it has.
I'm becoming convinced to just use short-lived (1-2h) JWTs for authentication and a long-lived (7-30d) refresh token. Just store the refresh token as secure as it's possible (httpOnly, secure, sameSite cookie) and the JWT in localStorage. This way the JWT is a little bit open for attacks (i.e. XSS), but even if they got stolen - they're valid for a very short time. According to the refresh token - not an expert but by knowing how it works I think it's very hard to steal a httpOnly cookie. This way I can have very long user sessions which are pretty secure! And I'm not losing a lot by adding a stateful refresh token, because authentication still works on stateless JWTs, so on every API request I still don't have to query the database for user data.
Yes! I like the sound of this approach. 2 jwt tokens, one in localStorage for use in feather's client, one in cookies for use in a special /refresh endpoint. Post a gist or a repo if you figure out how to do this in feathers.
Regarding statelessness, never quite saw the strength of that argument. For a lot of application's you'll be fetching the user object as part of the request anyway, for permission checks or similar. In general, fetching the user object is not gonna be the first bottleneck you typically run into when developing an app. So looking things up like seq or jwt_version in the user DB seems ok to me.
Or yeah, another approach is to use jwt in localStorage and then server side sessions for the refresh token feature.
I'm working on auth v3 which will also include an automatically generated refresh-token and the ability to more easily blacklist tokens. You can then either blacklist any tokens or specifically only a certain type. The recommendation is to at least have the longer lived refresh tokens to always be blacklist-able but it can also be done for any JWT.
Finally using this in production ;) Write up for anyone interested: https://www.kn8.lt/blog/dynamic-session-length-in-feathers-for-optimal-ux/
With v4 released and @KidkArolis great article I think we can close this now.
I realise this is now closed but thought I would mention how I ended up solving this.
After reading through the various ideas here and trying them out, I ended up settling for the tracking the users activity and basing my solution on a "lastSeen" calculation (our system is logging all activity anyway - so this was easy to obtain). I decided I didn't want the extra effort of double JWT's or actually updating the JWT on the client side.
However, what was different about the approach I used was to set the expiresIn to the longer timeout (e.g. 60 days) and then have another configuration variable (activityTimeout) set to the shorter period (e.g. 5 days). This means there is no re-issue of the token at all. The current date/time is checked against the lastSeen value and, if the difference between them exceeds the shorter period (that is there has been no activity for more than the shorter period) then the token is deleted causing the appropriate exception to be thrown (and captured in the client to redirect to the login request).
So, putting that more in "plain english".... "Your JWT expires in 60 days, unless you remain inactive for more than a 5 day straight period - in which case we force it to expire".
The only "flaw" I can see in this approach is if someone were able to "fake" a log record. However, log records are only written for authenticated users and they happen in an after hook. Even so, it would have to be THAT authenticated user that faked the log record. Of course, anyone with direct DB access could fake this fairly easily - but that is a different security breach.
I'm working on auth v3 which will also include an automatically generated refresh-token and the ability to more easily blacklist tokens. You can then either blacklist any tokens or specifically only a certain type. The recommendation is to at least have the longer lived refresh tokens to always be blacklist-able but it can also be done for any JWT.
I know this has been a while but i can't find anything on the topic of refresh-tokens and/or blacklist tokens coming from the official featherjs docs. Are these idea's dropped all together?
Most helpful comment
I'm working on auth v3 which will also include an automatically generated refresh-token and the ability to more easily blacklist tokens. You can then either blacklist any tokens or specifically only a certain type. The recommendation is to at least have the longer lived refresh tokens to always be blacklist-able but it can also be done for any JWT.