Although cross-site scripting (XSS) attacks get most of the web security press, cross-site request forgery (CSRF) is often much easier for an attacker to exploit.  Fortunately, ASP.Net MVC provides helpers to prevent these attacks.  Steven Sanderson has an excellent post describing CSRF and how ASP.Net MVC’s AntiForgeryToken helper prevents CSRF.

Unfortunately, security measures have a tendency to not only deter the bad guys, they also create some pain for everyone else.  The same is true for MVC’s AntiForgeryToken.  You can add AntiForgeryToken to your ASP.Net MVC web application to perfection and still leave the honest user seeing “HttpAnitforgeryException – A required anti-forgery token was not supplied or was invalid”. 

How can this be?  Well, I ran into this recently in the development of Team Web Access and found two interesting causes.

1. Browser restores last session on browser start up for page that is cached

If you have a page that is cachable that performs a post to your server (i.e. antiforgery will be on) and the user has their browser set to restore last session on start up (e.g. this option exists in the Chrome browser) the page will be rendered from cache. However, the __RequestVerificationToken cookie will not be there because it is a browser session cookie and is discarded when browser is closed. Since the cookie is gone you get the anti-forgery exception.

Solution: Return response headers so that the page is not cached (i.e. Cache-Control:private, no-store).  If your page returns quickly from the server (which ideally it should) the impact of this change will be unnoticeable.  If this page takes a long time to render on the server than this solution will be less than ideal.

2. Race condition if opening more than one tab on start up to your site

Browsers have the option to open a set of tabs at start up.  Moreover, if you apply Windows updates with Internet Explorer open it will restore all of your open tabs.  If more than one tab hits your site that returns a __RequestVerificationToken cookie you can hit a race condition where this cookie is overwritten. This happens because more than one request hits your server from a user that does not have the request verification cookie set. The first request is handled and sets the request verification cookie. Next the second request is handled, but its request also did not have a cookie in the request headers so the server generates a new one. The new cookie overwrites the first one and now the first page will get an antiforgery request exception when it next performs a post.  The MVC framework simply does not handle this scenario at this time, so you need to implement your own solution if your customers are hitting this scenario. This issue has been reported to the MVC team at Microsoft. 

It took quite a bit of logging on the server and questions to Microsoft internal and Team Foundation Service Preview users to track down these two causes.  It was important to us to understand and solve this issue because we were getting regular reports of it from our customers.  We did not want a security measure meant to deter a hacker to cause pain to the honest customer using Team Web Access.