You need the token to be sent in the request outside of the cookie mechanism (e.g. POST data). If sessions are managed via a session ID contained within a cookie, your approach doesn't prevent CSRF.
In your current implementation:
- save token to session;
- save token to client's cookie via Set-Cookie header.
This will have the effect of the token being saved in two locations, but both locations will be identified by cookie values.
e.g. if the token is "12345678
" it will be saved in session (pseudocode):
Session["SessionCSRFToken"] = "12345678";
and in cookies
Cookies.Add("CSRFToken", "12345678");
However, in order to save the value in session, the web application framework will create a cookie to identify the session, so you will get two cookies set:
Set-Cookie: CSRFToken=12345678;
Set-Cookie: SessionID=987654321;
If a user is logged into your website at www.foo.com
and then the user visits www.evil.com
, there could be a hidden form
tag embedded into the page of www.evil.com
(possibly within a hidden IFrame) which is automatically submitted by JavaScript:
<form method="post" action="https://www.foo.com/Account/DeleteAccount">
</form>
When the browser automatically submits the form, both cookies will be sent with the request
(CSRFToken=12345678
, SessionID=987654321
) and your cookie token (CSRFToken
) and session token (SessionCSRFToken
) will match and the effect will be that the user's account is closed on your system without their knowledge.
To prevent this, you need to store a CSRF token in the page itself (e.g. within a hidden input). This way your form could only be submitted as follows:
<form method="post" action="/Account/DeleteAccount">
<input type="hidden" name="CSRFToken" value="12345678" />
</form>
Now www.evil.com
has no way of including the hidden input within their own HTML as they have no way of knowing the value of the CSRFToken
. The value of CSRFToken
can be the same as the cookie (the session value is now no longer needed), but it only needs to be generated once pre user session (there is no need to constantly regenerate the value).
For each POST
you simply need to make sure that Request.Form["CSRFToken"] == Request.Cookies("CSRFToken")
.