After reading about how the CSRF protection works in Rails, I tried to trigger CSRF protection by doing this:
Note: We are using cookie based sessions.
I expected this to fail, because the 2nd tab generated a new, different CSRF token. When the login form submits, shouldn't the token that gets submitted to the server be an old, stale one?
However, this does work:
In this case, I get an InvalidAuthenticityToken exception as expected. Why?
Source: https://medium.com/rubyinside/a-deep-dive-into-csrf-protection-in-rails-19fa0a42c0ef
The CSRF token in the meta
tag is actually a concatenation of two strings: a "one-time pad" generated per request, and the "real" CSRF secret XORed with the one-time pad. See in the diagram below how the one-time pad is prepended to the XORed string in the masked token, which gets stored in the meta
tag:
Rails stores the CSRF secret in a session cookie without XORing. Javascript should be used in the browser to read the masked token from the meta
tag and pass it in the X-CSRF-TOKEN
header.
When Rails validates a request, it:
X-CSRF-TOKEN
header to retrieve the one-time pad and XORed string.This is why you are seeing changing tokens in the meta
tag -- the one-time pads are different. If you validated the tokens, you would find the same secret in both tokens.
Note: This one-time pad business might seem unnecessary. Anyone can retrieve the real secret if they have the masked token. Surprisingly, the purpose of the XORing is to change the CSRF token on every request so an attacker can't use timing attacks to discern the secret. See this paper on the BREACH SSL attack.
As noted in @max's comment, logging out deletes the session cookie. The next request generates a new CSRF secret which no longer matches the older masked tokens.