JWT with SPA without a server
Asked Answered
F

1

6

The application is a SPA that is hosted on static storage (conceptually similar to S3, although it's not S3) and has no backend server at all. Assume this is https://static.example.com/app.html

When users visit the page, they can authenticate with an external provider like Auth0 and Azure AD. They complete the authentication flow and are sent back to the SPA with an id_token on the URL fragment. For example, https://static.example.com/app.html#id_token=XX. That id_token is used to call an external API server, passed in the Bearer authorization header.

The issue is where to store the JWT in the client.

  1. It's a known fact that storing the JWT in sessionStorage could lead to the tokens being stolen with XSS attacks (or malicious code added in a dependency, etc).
  2. The recommended approach would be storing the JWT in a cookie that is set to HttpOnly, or at least part of it (see the "Cookie Split" section). However, this is not doable in my case, as there is no backend server, and after being authenticated users are redirected to the SPA directly, so I can't create a HttpOnly cookie.
    A variant of this method is what OWASP recommends: using a "fingerprint cookie". This has the same issues since I can't set a cookie that is HttpOnly.
  3. Another approach, as for example suggested by the Auth0 documentation, is to keep the JWT in memory. While this should prevent theft with most (if not all?) XSS attacks, it's unpractical because the session would be limited to the current tab, and would not survive a page reload.

I see two different options, all with serious or potentially serious drawbacks:

  1. Store the token in sessionStorage anyways, assuming the risk that in case of XSS attacks (or a malicious dependency injected via NPM) could lead to sessions being stolen. This could be mitigated by setting a short lifespan to tokens (e.g. 1 hour). While the app I'm working on doesn't store critical information (it's not banking or similar), a mistake in the code that let sessions to be stolen via XSS would not be nice.
  2. Implement a backend server to move the authentication flow there, maybe even replacing JWT's with session tokens entirely. This would make the application not static anymore, however, and it's undesirable.
  3. (The third option, keeping the JWT in memory, is excluded because of the poor user experience)

What am I missing?

Fairtrade answered 19/10, 2018 at 0:58 Comment(3)
In short, nothing. :) This is a good summary.Vanillin
I wish there were an alternative :(Fairtrade
I landed on this question after a long time and it is disheartening to see we still don't have a proper specification for this problem. I don't really want to have the overhead of having a server on client application only for this security purpose. I hope my upvote reach out to an expert who has a solution for this long going puzzle.Pneumatograph
V
3

Security is a compromise - you can choose to accept the risk of XSS, or to take the burden of a backend server for more security, or to sacrifice user experience for a level of security probably inbetween the two. You can't have everything.

One usual solution is to have very short-lived tokens in sessionStorage, and an httpOnly cookie for the identity provider to get a new token when needed. This way stealing a session token provides less value for the attacker, and as XSS needs user interaction, it may sometimes be difficult for an attacker to get hold of a new one (or easy, depending on where the XSS is). Also this solution needs more graceful error-handling and results in slightly more complex code.

Vanillin answered 19/10, 2018 at 21:2 Comment(2)
Thanks for answering. I was honestly hoping there'd be a "fourth way" that I didn't think of, that would let me have my cake and eat it too. If I'm using an external IDP like Auth0 or Azure AD, I can always send the users back to get a fresh token. This had been the plan all along. The IDP manages the cookie so I don't need to worry about that. This will break the user workflow, so token expiration should be short enough to guarantee security, but long enough to not force too frequent refreshes. I'm thinking 1hr could be good.Fairtrade
PS: To be clear, I plan to guard against XSS no matter what. Even if I store the JWT in a cookie, XSS attacks can still damage users in other ways (incl. installing malware, potentially).Fairtrade

© 2022 - 2024 — McMap. All rights reserved.