5

When storing a JWT for authentication in a web application, my first instinct would be to store it as a "hardened cookie", meaning all the required flags such as "HttpOnly" and "Secure" being set. This would still allow me to make use of XHR, as XHR requests to my own domain with .withCredentials(true) would still include the cookie, while also making sure that an attacker with XSS capabilities wouldn't be able to steal the token.

When reading the OWASP Cheat Sheet on JWT storage, specifically the section about sidejacking it reads as follows:

Symptom
This attack occurs when a token has been intercepted/stolen by an attacker and they use it to gain access to the system using targeted user identity.

How to Prevent
A way to prevent it is to add a "user context" in the token. A user context will be composed of the following information:

A random string that will be generated during the authentication phase. It will be sent to the client as an hardened cookie (flags: HttpOnly + Secure + SameSite + cookie prefixes). A SHA256 hash of the random string will be stored in the token (instead of the raw value) in order to prevent any XSS issues allowing the attacker to read the random string value and setting the expected cookie.

IP addresses should not be used because there are some legitimate situations in which the IP address can change during the same session. For example, when an user accesses an application through their mobile device and the mobile operator changes during the exchange, then the IP address may (often) change. Moreover, using the IP address can potentially cause issues with European GDPR compliance.

During token validation, if the received token does not contain the right context (for example, if it has been replayed), then it must be rejected.

When looking at the provided code example, this essentially means the token itself is stored in an insecure way, but the token is associated with a "fingerprint", which is stored in a secure way.

This raises the following question for me: If the JWT is useless without the fingerprint cookie, then every request where the JWT is being used also has to include a hardened cookie. Therefore, in order to "use" the JWT, a hardened cookie needs to be used. Why not store the JWT directly in the hardened cookie? Why add this extra convoluted step?

2
  • Just out of curiosity: .withCredentials(true) means you're using a Node.js app? Mar 23 at 19:22
  • @SirMuffington No, that's a normal JS function for XHR
    – user163495
    Apr 12 at 17:25

1 Answer 1

3
+100

The main reason not to use a cookie for the session token - be it a JWT, an opaque random blob, or something else - is that it puts you at risk of CSRF. There are many ways to mitigate CSRF (though it's worth noting that the SameSite cookie flag is not a total protection; even leaving aside older browsers that don't support it, cookie "sites" are much broader than origins). There are lots of questions on this site concerning CSRF; I'll leave the topic of addressing it to them.

The second reason you might ever store a JWT (in particular) somewhere other than a cookie is if you want to access its fields client-side. JWTs aren't just for session identification; they can store arbitrary data that you want to have visible to the client, ranging from the user's name or email address to credentials for various third-party services. OIDC JWTs are based around the model of transmitting user info in the JWT. Of course, you can read JWTs (or other data) out of cookies if you don't make them HttpOnly, but at that point using session/local storage makes more sense anyhow. Obviously this doesn't apply if the JWT is just an access token, unless perhaps you want to query its expiry time from the client.

The whole "sidejacking" thing is only a concern with JWTs (or other tokens) stored where JS can access them, so it's completely irrelevant for an HttpOnly cookie (as you surmised). The fingerprint cookie is a way to get the anti-token-theft property of HttpOnly cookies, while maintaining the JWT (or other access token) in a JS-readable location (usually not a cookie, which inherently protects against CSRF).

It's also worth noting that while token theft / sidejacking gets a lot of attention, in practice XSS is tremendously dangerous even without such risks. While defense in depth (in the form of preventing token theft) is useful, your effort is far better spent on preventing XSS in the first place (via context-sensitive output encoding, strict input validation, avoiding reflecting user content in dangerous contexts at all, and the use of a tight Content Security Policy).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy