I have a problem with the anti CRSF MVC mechanism. The cookie and the form input returned does not match. I'm getting an error every single time, only in one specific page. In the rest of the application it works well.
The server is returning HTTP 500 Internal Server Error
and I can see on the log this exception:
[System.Web.Mvc.HttpAntiForgeryException]: {"A required anti-forgery token was not supplied or was invalid."}
This is the hidden input that the server is generating is:
<input name="__RequestVerificationToken" type="hidden" value="QK8P7rjyZE6Vm5seY7Fr704YCOoFGdTIMzl1W7R0ZFpXSMjGKLG2T05DfFSYTxvtQCEx7DDT69DGsDB2+ZXFHY8oAjiKz0gw8BhDFywgmfIpoXnGpj7fONNzIIfvbrDrE9WJsMu6Io/0bDLM5WfKs0zktiNjyOWpfYrmnfINYmjW8NLOZFoz74xTcgTptAld">
And this is the Cookie returned:
Set-Cookie:__RequestVerificationToken_L2VGbG93=skmTAVI8HCbfxDS+xhioIMIISL3UOBI7qJM1JbHjTtAqKl4W70pDUcTKMm0p3R3mrHDziE8vXw0C0OO4HArzWO1/e6py+v/cFdbe9maFgjl4jMiZ9Wc4YIhC6+IUXkk6yqJDJ8dCIr8qtGaYcD9IX+m7/SlVhu521KQSWJYRcaY=; path=/; HttpOnly
When I examine what the server is sending, the cookie is exactly the same, but the payload has different encoding I think:
__RequestVerificationToken:QK8P7rjyZE6Vm5seY7Fr704YCOoFGdTIMzl1W7R0ZFpXSMjGKLG2T05DfFSYTxvtQCEx7DDT69DGsDB2%2BZXFHY8oAjiKz0gw8BhDFywgmfIpoXnGpj7fONNzIIfvbrDrE9WJsMu6Io%2F0bDLM5WfKs0zktiNjyOWpfYrmnfINYmjW8NLOZFoz74xTcgTptAld
The differences are in two characters that appear encoded:
/ -> %2F
+ -> %2B
Those are the only differences I can find between the hidden input field, and the post payload.
What could be the problem that is causing that ValidateAntiForgeryToken
fails in verify the token?
Regards.
I've had and resolved several issues with ValidateAntiForgeryToken
lately, so I'll share my findings with you.
Salt: Since you mention this only happens on a single page, my best guess is that you are using different salt
values in your calls to Html.AntiForgeryToken(salt)
and ValidateAntiForgeryToken(salt)
calls.
AJAX: as another answer has said, using AJAX may require extra work to ensure the token is included in the POST. Here is my favorite simple, automatic solution to add the token to all AJAX POST requests.
In your question though, you state that you have verified that the token is sending. Have you verified that you're only sending the token once? I found out that an AJAX call of mine was sending the token twice, which combined the values, and caused it to fail.
Machine Key and Cookies: this issue is ugly, easy to spot (causes exceptions), but not very intuitive. The validation cookies and tokens are encoded and decoded using a unique "machine key". This means that if you have a server farm, or change your server, your cookie will no longer be valid. Closing your browser fixes the issue (because the cookie is a session cookie). However, some people leave their browser windows open in the background for a long time!
The solution is to set a "machine key" in your config file. This will tell MVC to use the same key on all servers, ensuring that the cookie will be decryptable everywhere.
Encoding Bugs: using a testing utility called jMeter, we attempted to load-test our pages, only to find out that it had a bug that caused our token to have 2 extra "
around the value.
The solution is to lower your trust in your tools! Test in a browser, and if that works, create a test that extracts the token and cookie values, and set a breakpoint to verify the results.
If none of these things work for you, then I'd recommend taking a look at the MVC source code for ValidateAntiForgeryTokenAttribute
, specifically the OnAuthorization
method. It will help you see the different steps where validation could fail. You might even inspect your error's Exception.StackTrace
to determine which part is failing.
As a side note, I really dislike the implementation of ValidateAntiForgeryToken
in MVC, because:
Page
and creates an artificial ViewState
to encrypt the tokens and cookies. Seems overkill.So, I grabbed the source code, and created my own specialized subclass, which also turned out to be very helpful in debugging its issues, because I could set breakpoints on the validation methods, and it was really easy to determine which validation step was failing.