What's the whole point of a JWT refresh token?
Asked Answered
I

1

30

I've been reading about this for a while, and nothing makes sense, and the explanations are conflicting, and the comments are proving that.

So far what I understood is that JWTs are storing information encoded by the server, can have expiry times, and the server with its secret key can decode the information in it if it's valid. Makes sense.

It is useful for scalability, so independent APIs can decode, and validate the information in the token, as long as they have the secret key. Also, there's no need for the information to be stored in any database, not like in sessions. Makes sense.

If the token gets stolen, the API has no way to tell if the token is used by the right person, or not. It is the downside of the above.

By reducing the expiry time of a token, the security vulnerability can be reduced, so thieves have less time to use the tokens without permission. (side question, but if they were able to steal it once, they will probably do it second time as well)

But reducing the time of how long the token is valid means that the user will need to log in every time the token expires, and as from above, it's quite frequent, so wouldn't provide too good UX. Makes sense.

From now, nothing makes sense:

Introducing a refresh token would solve this problem, because it has a longer expiry time. With the refresh token access tokens can be generated, so the user can be logged in as long as they have the refresh token - which is for a longer period of time -, while a stolen access token is still only valid for a short time.

For me the above seems like an extra layer of complexity without any improvement in security. I.e. for me it seems like the above equals to a long-living access token.

Why? Because for me it seems the refresh token is basically an access token (because that's what it generates). So having the refresh token means unlimited access tokens, so unlimited access to the API.

Then I have a read an answer that there's a one-to-one mapping of refresh token, and access token, so stealing the access token still means unauthorised access to the API, but only for a short time, and stealing the refresh token would generate a different access token, so the API could detect the anomaly (different access tokens are used for the same account), invalidating the access tokens.

It seems like I'm not the only one who's confused about the question.

If the above is not true, how refresh tokens really help?

If the above is true, and there really is one-to-one mapping of refresh tokens, and access tokens:

  • it completely loses it's benefit of being "stateless"
  • the user cannot be logged in from multiple devices (it would have been an "anomaly")
  • I can't understand how an access token could be invalidated - is there a session ID stored in the token data, or the user is "blocked"?

It would have been really great if someone could clear the question, because from 5 explanations, 5 conflicting statements are (sometimes the same explanation contains conflicting information), and many developers want to understand this method.

Illustrational answered 1/11, 2021 at 16:51 Comment(0)
A
16

There is this general confusion around token-based auth, so let's try to clear some of it up.

First, JWTs are not just "encoded" by the server, they are "signed" (which more precisely is message authentication usually). The purpose is that such a token can not be altered or changed by the client, or by anyone, so any field (claim) in the token can be trusted to be as the issuer created it, otherwise validation will fail.

This yields two important takeaways:

  • validating tokens is important (obviously) in any implementation
  • the contents (claims) of a JWT are not encrypted, ie. it's not a secret and can be viewed by the client

Such a token can be used to maintain a session without server-side state, if it contains some kind of an identity for the subject (user, like a user id or email address), and an expiry.

Another important takeaway though:

  • Logout (immediate session invalidation) is not possible in a stateless way, which is a drawback. To be able to log out as in invalidate an existing session, the server must store and check revoked tokens, which is necessarily a stateful operation.

Also a JWT token is typically stored in a way that it's accessible for client-side code (javascript), so things like who the user is and when the token will expire can be read by the client app. It need not be so, yet most implementations do this, eg. store it in localstorage. This makes these tokens susceptible to XSS attacks, meaning that any successful XSS will be able to get the token.

For the reasons discussed so far, JWT authentication is inherently less secure than a plain old session, and should only be used if there is a need. Many times when token auth is used, it is not actually necessary, just fancy.

Sometimes such a token is stored in a httpOnly cookie, but in that case the token cannot be sent to multiple origins (one benefit of localStorage) and a plain old session id could also have been used, and would actually be more secure.

Ok, so what are refresh tokens. As you correctly stated, limiting the lifetime of an access token is useful to limit the validity of a compromised token. So a refresh token can be used to get a new access token when the old one expired. The key is where these are stored.

A key takeaway:

  • If a refresh token is stored the same way as the access token, it usually doesn't make any sense. This is a common mistake in implementations.

In a better architecture, the following can happen:

  • There are (both logically and "physically" as much as it makes sense in today's cloud world) at least two separate components: the identity provider (IdP, or "login service"), and the resource server (eg. an API).
  • When a user logs in, they actually create a session with the IdP. In this case either a plain old session id (acting as refresh token) or an actual JWT refresh token is set up for the IdP origin (domain name).
  • An access token is then created when needed for the resource server origin, using the existing session with the identity provider.
  • Now even if there is a total compromise of the resource server, like in case of successful XSS, the refresh token belongs to a completely separate origin, so cannot be accessed by the attacker. Even if it's the same origin, but the refresh token is in a httpOnly cookie, that helps, because the attacker then needs to be able to perform repeated XSS against a victim user to receive new access tokens.

There can be implementation variants of this, but the point is the above, separation of access to the two tokens.

A one-to-one mapping of refresh tokens to access tokens as you described would I think be unusual and also unnecessary, but one session per user is in fact sometimes a requirement (especially in financial applications where you want to have a very clear audit trail of what a user did). But this is not much related to the things discussed above.

Also as stated above, proper logout (session invalidation) is not possible in a stateless way. Fortunately, very few applications actually need to be truly stateless on the server-side.

Aniakudo answered 2/11, 2021 at 19:38 Comment(9)
For me based on the above session based authentication makes a lot more sense in every way (refresh token flow seems very similar to a session). I'm using websockets, and validating stateless tokens is quite a bit of pain in them as I have reconnect to them on every access-token replacement because I can only validate the token in the httpOnly cookie when the connection is established. With sessions, I can just extend the session validity on every request, and query if it's still valid before sending a message on websocket. What are your thoughts?Winne
For that usecase, plain old sessions are a lot simpler and more secure too. Tokens have a role when you want something like SSO, an identity server different from resource server, or multiple resource servers. For simpler scenarios, you just don't usually need JWTs.Aniakudo
@GaborLengyel I know this is an old question, but I am still unclear about something, and would love it if you could clear it up, let's say: the access token is stolen, after sometime it expires, then attacker makes a request to the api, api sees an expired token, asks for a new token from the idp, then returns it to the attacker. I don't see how the api can figure out if the request is coming from the attacker or actual user without some stateful logic like monitoring the ip, activity, etc.. so in that case I truly don't see any need for a refresh token.Yearning
@ThabetSabha Who is the attacker though? Let's assume it's another person (a 3rd party), then they will not have a valid session with the identity provider (IdP), ie. they will not have access to the refresh token - that's the whole point, this is why you need to store it differently. That means a new access token will not be issued. In other words, an access token is for the service itself, while the refresh token is for the identity provider, and the latter would be stored for a different origin, maybe in httponly cookies so it's harder to get.Aniakudo
@ThabetSabha What I suspect you might have been thinking about is the attacker performing XSS to not just get the access token but also to exploit the refresh token. But that would not work, because the refresh token should not be accessible on the service origin (due to httpOnly), and the identity provider should not be vulnerable to CSRF, so such a request cannot be crafted.Aniakudo
Also even if that's possible, that would require further user interaction, the attacker cannot do it by themselves, which adds some thin layer of protection. (XSS might not be so easy to exploit in a user's context, they would actually need to do something, and that limits the opportunity for an attacker as opposed to not having a refresh token and gaining access to a much longer lived access token.)Aniakudo
@GaborLengyel Thank you you so much for responding! I think this clears everything up, the main reason for my confuison was because I remember reading that even httpOnly cookies could be stolen in case of an XSS attack, but checking it now that seems to be outdated.Yearning
@GaborLengyel this is an awsome answer! There is actually a way to log out, even if not perfect. If a token is valid for an hour, and a user has the access removed, worst case an hour later will be logged out. IDP will just not return a new access token for refresh token. Refresh token required revalidation of access back at IDP every now and then.Elegancy
@PiotrGwiazda Sure, I meant immediate logout (like when as a user I know my session is compromised, eg. an attacker got my token) or forced logout (eg. by an admin).Aniakudo

© 2022 - 2024 — McMap. All rights reserved.