2
votes

I am struggling to understand how JWT refresh tokens are safer than just using normal JWT access tokens with a long lifespan. I understand that by keeping the lifespan of JWT access tokens short, it limits the window of opportunity for attackers to abuse it. This assumes that the SSL layer of HTTPS has somehow been bypassed by the attacker in order to gain the JWT access token in the first place.

How do JWT refresh tokens solve this problem exactly? Once the access token expires, you would have to transfer the refresh token, which can also be hijacked if we assume that HTTPS is not secure enough. If the attacker gains control of the refresh token, then he now has access to a large supply of access tokens since refresh tokens typically have a long lifespan. By extension we could also say that the initial username/password authentication can be stolen if the HTTPS protocol is compromised.

Since the refresh token must be kept in the frontend (I am building a Angular/Spring boot application), we must take extra care that the refresh token can not be stolen client side as well. LocalStorage is clearly unsuitable to store the refresh token since it is not meant to be a secure storage. They are also unsuitable to be sent every request since they would be stolen together with the access token otherwise, which defeats the purpose of having short lifespan access tokens in the first place. Where should one store the refresh token?

If I wish to provide remember-me functionality at the loginpage, can I simply set the refresh token with an infinite lifespan?

I already went through several well written answers from the following links (and more):

What if JWT is stolen? SPA best practices for authentication and session management https://security.stackexchange.com/questions/119371/is-refreshing-an-expired-jwt-token-a-good-strategy

But I am left unsatisfied regarding these 3 questions.

2

2 Answers

2
votes

I shall attempt to answer all the points in your question

  • Do not use JWT refresh tokens. Use Opaque refresh tokens. Typically, JWTs are to have very short life times. The reason for this is that revoking them may not be possible if you do not have blacklisting

  • You can store refresh tokens in HttpOnly, secure cookies. If you want to avoid CSRF and XSS, then you can split the access token and store half in cookies, and other half in localstorage

  • If you assume that https is compromised (which is actually possible), the best defence here is to have measures in place to detect stolen refresh tokens. You can do do by implementing rotating refresh tokens. This can also be used to implement remember me functionality quite easily and with the highest level of security.

In general, this topic is quite complex and it would be impossible for me to explain everything here. So here is a blog post I like that explain everything todo with session security. They also have an open source library called SuperTokens you can use that is by far the most secure implementation I have seen. They have it in various tech stacks, and can also implement one for your tech stack.

1
votes

You've already received an answer and have selected it, but I thought I'd add another perspective.

I'll start by pointing out a bit of myth with one of your assumptions:

LocalStorage is clearly unsuitable to store the refresh token since it is not meant to be a secure storage.

I'm sure some will disagree with me on this, but to me LocalStorage is just as secure as Cookie storage, if not more.

Cookies are susceptible to CSRF attacks, while LocalStorage not so much. And both LocalStorage and Cookies are susceptible to XSS attacks (even httpOnly cookies, since injected code could perform any operation withCredentials).

So from that perspective, Cookies offer a greater attack surface than LocalStorage.

Therefor I don't see any problem in storing access NOR refresh tokens in LocalStorage from purely a security perspective.

Beyond the security concern, you may need to store them in LocalStorage (or a non-Cookie store) depending on the platform(s) you deploy to, ex: some mobile frameworks don't support Cookies.

Conversely, if you plan to run a JS Web app that does server-side rendering, you may need Cookies since typically the server process will not have access to LocalStorage.

So the issue is not entirely one of security.

As for the main gist of your question, which I understood as:

If access tokens are susceptible to attacks, what makes refresh tokens helpful, as they too must be susceptible to the same attacks?

You're right. Both access tokens and refresh tokens can be compromised. The question is... what can your server do about it once it finds out?

The idea of access tokens and refresh tokens is that access tokens are short lived and refresh tokens are long lived.

Personally, I see little use in refresh tokens unless you're using JWTs as your access token, which is what you eluded to in your post.

As you probably know, JWTs are stateless (albeit you can implement white/black lists which would make them stateful, but that sort of defeats the purpose). Therefor there is nothing the server can do to disable a stateless JWT.

Due to this fact, some consider it risky to have long expirations on JWTs because they can't easily be disabled if compromised. I agree with this.

So to get the "best of both" worlds, one can use short-expiry JWTs (10 minutes or so) with long-expiry refresh tokens (many OAuth implementations never expire the refresh tokens).

This strategy gives your server some control back by allowing it to deny issuing new refresh tokens, thereby denying new access tokens, while also benefiting from some of the selling points of JWTs.