Search code examples
node.jssecuritysessioncsrfexpress-session

Why are CSRF tokens encrypted?


I'm working on implementing secure CSRF tokens into my nodejs backend & react frontend app that uses express-sessions. I have created this module to generate, validate, and store CSRF tokens in redis & include some extra security like separate secret per token for BREACH security, per-feature tokens, & support for multiple tabs of the same feature (thus the token IDs and keys).

I have read that CSRF tokens are encrypted and only the server knows the key, so that when the browser sends the token to the server the server can validate via the secret key that only the server knows. My question is why are they encrypted?

To my understanding, if an attacker somehow manages to steal the encrypted token then the encryption is useless, as when they submit a request w/ the token the server would validate it since it is essentially the same encrypted token. If that is the case, wouldn't it be more performant to just store the token on the server as well and check if the one the client submits matches? (Of course accounting for timing attacks in the comparison)

Thank you


Solution

  • CSRF tokens are not normally encrypted. In a textbook implementation using for instace the synchronizer token pattern in a normal web app, a CSRF token is just a sufficiently large random value, stored on the server and also given to the client upon form generation. The client can then send it back with the form, proving that the form it is sending was actually generated by the server, and not somebody else. (Even in case of other patterns like double posting, the token sent as something like a header field and a cookie is just a random token in the base case.)

    However, there are two things to note.

    The synchornizer token pattern (a classic CSRF token) needs a server lookup. This is not so big of a deal if there is a user session anyway, but that's not always the case, some applications are designed to be stateless. In that case you can't just have a random token, because you can't decide without checking server-side state whether it's valid.

    The other thing to note is that you can actually increase security even more, if the token contains some information about the client. Like for example it's less useful to somehow steal a csrf token, if it's somewhow tied to the client anyway (for example if it is associated with the current client IP address in a more naive implementation). Again, you could store this additional information server-side, but that's again state, that some applications want to avoid to make things like load balancing easier.

    So it comes down to stateless CSRF tokens, ones that you can just check as they are, without state (database) lookups on the backend.

    What you can do (and what some frameworks do for you) is they create a structured token with some data embedded in it, and they encrypt it with a key only known to the server. The server then sends this as the CSRF token, and expects to receive it back upon state changing requests. When it receives it back, the server doesn't need a database lookup, it can just decrypt the token and see if it's a valid one that the server created.

    Note that purely for this purpose, you don't actually need encryption, the crypto primitive more suitable would be a message authentication code, because you only care about the authenticity of the token, ie. that the server itself created it and not somebody else. However, the data some frameworks include in the token is many times further protected by encyption (and implicit message authentication by a suitable authenticated encryption algorithm). But in a very basic implementation, you could actually just include a timestamp and user id with hmac as a stateless CSRF token (but including more information, maybe even about the form fields generated would further increase security).

    So in short, unencrypted random tokens are considered sufficient for CSRF, and in case of double posting, they can also be stateless (because of the same origin policy, an attacker cannot post the same random token to a different origin both as a cookie and as a header). But an encrypted, more information rich token can provide more security if that's needed, potentially even somewhat mitigating the stolen CSRF token threat too.