Rychu
Rychu

Reputation: 995

Why not to have two access_tokens and two refresh_tokens stored one in cookie and one in localstorage to protect from XSS and CSRF

Background

Scenario is that we have a REACT SPA and a single API which is both a resource server and an authentication server. We want to implement simple tokens based auths. There is no dedicated SSO server so no need for OAuth2.

I've read lot of articles for the last two days on how to do this correctly and there is one thing that still bothers me. There is a lot of discussion whether the tokens should be stored in the LocalStorage or a cookie? First is vulnerable to XSS attacks because injected code can steal your data from the LocalStorage but protects against CSRF because forged request from malicious website won't be able to steal it. The latter is vulnerable to CSRF because forged request from malicious website makes browser send the cookies along but protects against XSS because malicious website don't have access to LocalStorage of the original website.

Question: then why not use both?

Why nobody creates two JWTs signed with two different keys and two different refresh_token secrets? One JWT and refresh_token is then stored in the LocalStorage and the other in a cookie. If I ask google it's always one vs. another. Is having both an anti pattern or a bad idea in any way? Because it is not that much work to implement that...

How do I imagine this should work

Here's how I think such flow can work:

  1. User enters login and password on a login form
  2. React app sends login request with login and password to /api/auth/login. In return it gets:
  1. React app, with every next request, sends the first JWT as a value of Authentication header and the second one is attached automatically by the browser as a cookie.
  2. API checks signature of both and if both are correct then authenticates the request
  3. 3 - 4 repeats until any of the tokens expire (I assume their expiration time is the same but let's be more generic for the sake of this step through)
  4. React app sends a request to /api/auth/refresh with JWT and refresh_token from LocalStorage and browser attaches second JWT as a cookie and the other refresh_token as a cookie as well because the path condition for the cookie is met.
  5. API validates the signature of the expired JWTs and checks if both refresh_tokens exist in the persistence layer for this user and have not expired. If everything is fine new JWTs and also new refresh_tokens are generated and returned to the react APP same way as in point 2.

On logout refresh tokens are removed from the persistence layer so access tokens simply won't be refreshed.

Why do you need this refresh tokens if you think this flow is more secure?

I came to a conclusion that, indeed, adding refresh tokens doesn't increase security for users and have the drawback that there is this small gap after logout/revoke when JWTs are still valid but the refresh tokens are revoked/user logged out but have a lot of other pros that I managed to gather from many many discussions from this portal:

Upvotes: 3

Views: 533

Answers (1)

Rychu
Rychu

Reputation: 995

We have implemented this flow and here are two main conclusions:

  • This flow works great and gives more security when API and frontend are hosted on the same domain (e.g., api.example.com and example.com)
  • httpOnly cookies are blocked by the Safari browser when API and frontend are hosted on a different domain (e.g., frontend.com and api.com)

Upvotes: 1

Related Questions