To protect against CSRF you should put a nonce in a hidden field in the form, and in a cookie or in the session variable. But what if the user opens several pages in different tabs? In this case each tab would have a form with a unique nonce, but there would be only one nonce stored in the session variable or cookie. Or if you try to store all the nonces in the cookie/session variable, how would you identify which one belongs to which form?
You can store the same nonce in each of the forms. The easiest way to do it is to tie the nonce to the session ID, so that those forms only work in that session.
You will want to make it hard for attackers to snarf session IDs and create their own nonces. So, one way to go about it is to use HMAC-SHA256 (or the like) to hash the session ID, using a key that you do not expose to the public.
(Obviously if the attacker can get the actual session ID itself, they can already do session hijacking. So that's not what I'm talking about, but rather the ability for an attacker to craft a script (that runs on the victim's computer) that can somehow grab the session ID and use that to dynamically generate a URL with the nonce pre-filled.)
ETA: Whether the above approach is enough on its own depends on how long you expect your typical sessions to last. If users usually use long-lasting sessions spanning longer than a few hours, you'll need to use something more sophisticated.
One approach is to create a new nonce for each form, that contains the timestamp, as well as hash(timestamp . sessionid)
(where hash
is some variant of HMAC as described above, to prevent forgery, and .
is string concatenation). You then verify the nonce by:
- checking the timestamp to ensure that the nonce is fresh enough (this is up to your policy, but a few hours is typical)
- then, calculating the hash based on the timestamp and session ID, and comparing against the nonce, to verify that the nonce is authentic
If the nonce check fails, you'll want to display a new form, pre-populated with the user's submission (so that if they took a whole day to write their post, they won't lose all their hard work), as well as a fresh nonce. Then the user can resubmit straight away successfully.
Some people do generate a token for each form, and that is a very secure approach. However, this can break your app and piss off users. To prevent all XSRF against your site you just need unique 1 token variable per session and then the attacker will not be able to forge any request unless he can find this 1 token. The minor issue with this approach is that the attacker could brute force this token as long as the victim is visiting a website the attacker controls. HOWEVER if the token is pretty large like 32 bytes or so, then it would take many years to brute force, and the http session should expire long before then.