62

Conceptually, I really like JWT as it is in line with the statelessness of REST etc (no state saved server-side, all relevant data is contained in the token).

What I am unsure about: how would you handle token expiry when not connected (ie, a "remember me" functionality)?

There's an emerging coverage of JWT on the web, but I couldn't find anyone that answered the expiry question yet.

Clarification: I am not asking how to handle a token soon-to-expire, but what to do when a token has already expired (user closed website/app for a while). The simplest solution that comes to my mind is caching the user's credentials, which is rather insecure.

arnuschky
  • 2,159
  • 1
  • 19
  • 15
  • Great question arnuschky - did you ever find a good solution to this? – Lasse Christiansen Aug 26 '14 at 07:14
  • No, unfortunately not. I think the only possibility is to hand out another set of long-lived tokens for allowing re-login. Of course, this means to soften up the clean concept of JWT with methods from older (ie session based) login... :( – arnuschky Aug 27 '14 at 07:42
  • Okay - thanks for responding on this. I have created a bounty to hopefully attract some attention from people that might have solved this in a clean way. I will try to do some research on it and return when I have figured out which direction I will go. – Lasse Christiansen Aug 27 '14 at 09:32

6 Answers6

12

There isn't a black and white answer to the given problem of how to implement the remember-me functionality theoretical wise. Lots of theoretical arguments are given on why something should not be done, while at the same time there is no clear answer given to the problem as for how it should practically be done.

Remember-me implicitly comes with the problem that you need a longer time window of token expiration, there is just no way around this. The most secure way is to have the user login regularly given brief expiry; nobody practically likes this though, so a tradeoff is made where theoretical secure perfection is weighed against practical measures.

The way this tradeoff works is by patching the imperfections that come with having a long expiry for a token. But make no mistake about it, you will need a jwt / cookie with long expiry (whether you use two tokens, implement some secondary refreshing mechanism or whatever, at the end you run into the same problem)!
Based on articles I've read on how others did it, this is how it's done.

The way I will implement this is by offering a 3-month expiry on a jwt stored in an httpOnly / secure cookie when the user checks remember-me.
On logout just clear the cookie.
Obviously protect using https / CSRF measures on top.

If you don't agree with it, stop fussing and offer an alternative solution - which there clearly isn't from the infinite number of discussions I've read on it.
If there were a straightforward solution to the problem, there probably wouldn't be so many discussions about it in the first place.

Edit:

You can use a refresh token mechanism, but it only works if you verify the time intervals when the refresh token gets used. For example, depending on the lifetime of the access token, you can govern how often you allow a user to refresh the token. If the intervals show suspicious activity, you should revoke both the tokens for all clients.
The problem revolves around the idea that either access token or refresh token would theoretically be stolen. So merely having a refreshing mechanism without additional security is essentially useless. If you can steal an access token, you can steal a refresh token too.

html_programmer
  • 18,126
  • 18
  • 85
  • 158
11

I am not so sure if I follow but I will write what I think.

Imagine the token as a hotel card, you pay in advance for 5 days (remember me set to expire on 5 days). I can enter the building, garage, room, etc. within those 5 days, after those 5 days, it won't work anymore.

What to do when token has already expired? Nothing at all.

Imagine I pay those 5 days and meh, I had an urgency and I go back home (with the card on the pocket). The hotel doesn't care at all, when the 5 days pass, the card is just an useless piece of plastic and if you try to use it on the hotel, it will do nothing.

So back to web development. If you offer a remember me service, you can put an expiry date to let's say 7 days. As long as the user has the token, he can access the service without any problem. If he loses the token, he needs to login again. If he uses the token and it have expired, he will need to login again too.

If he login, he gets a token for 7 days, if he doesn't use it anymore and after 20 days he comes again, he would need to login again, the server will just decline your petitions until you do so.

What I would do if you use something like angular on the frontend is to check the token validation on startup so you can have a nice user experience.

What I don't understand about your question is de caching thing though.

Jesus Rodriguez
  • 11,918
  • 9
  • 64
  • 88
  • 6
    The problem is that JWT tokens are self-contained. They cannot be invalidated on logout. For security reasons, they therefore usually expire after 2-10 minutes. Just before expiry, the app can request a new token using the soon-to-expire token. This all works well without persistent logins ("remember me"). But with persistent logins, we want the user to be able to log in after closing the browser tab or the mobile app, that is, after the original token has expired. I think the only choice is to use a second mechanism with different tokens for persistent logins... – arnuschky May 23 '14 at 08:25
  • 3
    You can invalidate JWT on logout. If you stored the JWT using HTML5 locale storage or cookies for example, just delete it. If you want to invalidate it on server side, you just have to set an id to your tokens ("jti" claim) and check on your server that the id is still valid. – Spomky-Labs Sep 02 '14 at 06:48
  • I think that many systems (security wise) could take the expired card after 20 days, take a quick look making sure the customers credit card is still valid and issue a new card without having the guest identify himself again. Or reject the request for a new access card if applicable. However, since many systems use the access token + refresh token design, I assume that there are some benefits with this over just using an expired access token as a request for a new access token. Not sure what that benefit is though... – Andreas Lundgren Oct 06 '16 at 21:41
  • If you store a JWT in a cookie/session, you MUST guard against CSRF. – Alan Feb 19 '18 at 19:45
  • @arnuschky even if they expire after 2-10 minutes you probably still have some refresh token to refresh it... it would be annoying for the end user to login again and again every 10 minutes. – Konrad Sep 24 '18 at 22:21
  • refresh tokens can be hijacked as well so you can't rely on JWT tokens only. In practice it won't work so you need some way to invalidate them – Konrad Sep 24 '18 at 22:22
  • thanks dude , very clear your point of view – Gabriel Martinez Bustos Nov 27 '20 at 22:04
  • " to check the token validation on startup" and then what? Doesn't solve the problem, as none of the answers on the entire internet do. – html_programmer Mar 16 '21 at 09:56
6

In addition to @Jesus answer, you can think about implementing a refresh token system: https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/

In the hotel-example, your hotel-card (access-token) would be invalid after time X, but at the reception you can use your passport (refresh-token) to get a new hotel card again.

You could store the refresh token in the database with additional data about the device the user is using, allowing him to disable the device in case it gets stolen.

Example:

  1. first correct client login: Create a refresh token which is valid forever (until it gets deleted or invalidated)
  2. store refresh token in database
  3. return access token (JWT) with expiration time to client ( this token gets not stored in database)
  4. for the next request, the client sends the access token

  5. Now Check if the access token is expired:

    5.1 Access Token not expired, all okay

    5.2 Access Token expired, check if there is a refresh token in database

    5.2.1 Refresh Token is in database, return new Access Token

    5.2.2 No Refresh Token in database, return 401 / logout, User has to login again

Hope this helps.

Felix Hagspiel
  • 2,634
  • 2
  • 30
  • 43
  • 1
    saving token to database makes it stateful. – Ryan Aquino May 20 '20 at 04:55
  • 4
    I don't see how that makes sense. If someone gets your jwt then even when it's expired, you will always be able to get a new jwt based on the refresh token when it is stored in the database for that user. – html_programmer Mar 16 '21 at 09:54
2

You need to persist the JWT on the client so that it’s available across page loads, the most secure strategy is an HTTPS-only cookie. This will send the JWT to your server on every request and the server can check the validity of the token and reject it if it's expired. How you handle the expiration is dependent on the type of web app you have.

For a single-page application (e.g. Angular.js apps) you would want to structure the application so that it makes an initial request of the server before it bootstraps the rest of the application. If the server sees that the JWT in this request is expired it would issue a 401 response. You application would respond to this response by rendering a login form. Otherwise it would continue with the assumption that the JWT is valid and can be used to access the required resources. If, at any time, the app sees a 401 it should bring the user back to the login form.

For traditional web apps that render their pages on the server: for any request that has an expired JWT (as read from the cookie) the server should issue a 302 redirect to a login form.

robertjd
  • 4,723
  • 1
  • 25
  • 29
2

I think what you are asking is how to invalidate a JWT server side for long expiry tokens (e.g. "remember me" functionality)?

I ran into this issue myself recently and ended up using a unique user secret to invalidate the token, when the user attempts to validate a token that was produced with an old secret it will fail. The username can be found in the decoded JWT pre verification.

You could probably even use the users password salt for this, that way any current JWT's would be invalidated when a user changes their password (assuming you also change the salt at the same time), this may be problematic though as the password hash and JWT's would become tightly coupled

  • 1
    You loose no-db-hit required benefit of JWT if you use the above approach. Not recommended. – Amit Tripathi Aug 24 '17 at 04:44
  • @AmitTripathi - to be honest JWT and session cookies both seem like overly designed systems to avoid database hits. Maybe I'm not seeing the whole picture... but the cleanest and most secure design, seems to be - keep sessions in the database, and bulk the requests, or use a single page web application that will reduce the number of session check requests. – tim-montague Oct 04 '20 at 07:06
2

I can think of one way, but it is not really defined the standard.

What about adding another kind of expiration date with different lifespan to the claims? With two claims, we can treat the shorter one of it as the resource access expiration date, and the longer one as the refresh expiration date, e.g.

{
    "iat": /* current time */,
    "bbf": /* current time + 1 hour -- expired means no resource access */
    "exp": /* current time + 1 week -- expired means cannot refresh */
}

(Note: I use bbf for the shorter expiration date. No specific reason, just because it has 3 characters in length.)

So with "remember me" checked, when the user reconnects, he can use the same token to request for a new one, but not to access the resource. With this, all relevant data is contained within the token -- no extra token required.

And lastly, when "remember me" not checked, just use the same lifespan for bbf and exp.

hiapay
  • 71
  • 1
  • 4
  • 2
    Correct me if I'm wrong but isn't this just as insecure as setting a 1 week expiry time for the token since someone with the token can use it to create a new token and gain full access anyway. I'm still coming to grips with things but isn't the point of a seperate refresh token so that it can be stored more securely on the client somewhere and the server can also maintain a list of these (less issued) tokens and revoke them at will? – Brendon Muir Apr 15 '20 at 21:39