I’m having problems with the AntiForgeryToken in ASP.Net MVC. If I do an iisreset on my web server and a user continues with their session they get bounced to a login page. Not terrible but then the AntiForgery token blows up and the only way to get going again is to blow away the cookie on the browser.
With the beta version of version 1 it used to go wrong when reading the cookie back in for me so I used to scrub it before asking for a validation token but that was fixed when it was released.
For now I think I’ll roll back to my code that fixed the beta problem but I can’t help but think I’m missing something. Is there a simpler solution, heck should I just drop their helper and create a new one from scratch? I get the feeling that a lot of the problem is the fact that it’s tied so deeply into the old ASP.Net pipeline and is trying to kludge it into doing something it wasn’t really designed to do.
I had a look in the source code for the ASP.Net MVC 2 RC and it doesn't look like the code has changed much so while I haven't tried it, I don't think there are any answers there.
Here is the relevant part of the stack trace of the exception.
Edit: I just realised I didn't mention that this is just trying to insert the token on the GET request. This isn't the validation that occurs when you do a POST kicking off.
System.Web.Mvc.HttpAntiForgeryException: A required anti-forgery token was not supplied or was invalid. ---> System.Web.HttpException: Validation of viewstate MAC failed. If this application is hosted by a Web Farm or cluster, ensure that <machineKey> configuration specifies the same validationKey and validation algorithm. AutoGenerate cannot be used in a cluster. ---> System.Web.UI.ViewStateException: Invalid viewstate. Client IP: 127.0.0.1 Port: 4991 User-Agent: scrubbed ViewState: scrubbed Referer: blah Path: /oursite/Account/Login ---> System.Security.Cryptography.CryptographicException: Padding is invalid and cannot be removed. at System.Security.Cryptography.RijndaelManagedTransform.DecryptData(Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount, Byte[]& outputBuffer, Int32 outputOffset, PaddingMode paddingMode, Boolean fLast) at System.Security.Cryptography.RijndaelManagedTransform.TransformFinalBlock(Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount) at System.Security.Cryptography.CryptoStream.FlushFinalBlock() at System.Web.Configuration.MachineKeySection.EncryptOrDecryptData(Boolean fEncrypt, Byte[] buf, Byte[] modifier, Int32 start, Int32 length, IVType ivType, Boolean useValidationSymAlgo) at System.Web.UI.ObjectStateFormatter.Deserialize(String inputString) --- End of inner exception stack trace --- --- End of inner exception stack trace --- at System.Web.UI.ViewStateException.ThrowError(Exception inner, String persistedState, String errorPageMessage, Boolean macValidationError) at System.Web.UI.ViewStateException.ThrowMacValidationError(Exception inner, String persistedState) at System.Web.UI.ObjectStateFormatter.Deserialize(String inputString) at System.Web.UI.ObjectStateFormatter.System.Web.UI.IStateFormatter.Deserialize(String serializedState) at System.Web.Mvc.AntiForgeryDataSerializer.Deserialize(String serializedToken) --- End of inner exception stack trace --- at System.Web.Mvc.AntiForgeryDataSerializer.Deserialize(String serializedToken) at System.Web.Mvc.HtmlHelper.GetAntiForgeryTokenAndSetCookie(String salt, String domain, String path) at System.Web.Mvc.HtmlHelper.AntiForgeryToken(String salt, String domain, String path)
You can achieve this by simply returning the AntiForgeryToken after they log in. No need to re-use the same token 2 times. You can use JS similar to this to load ONLY the new AntiForgeryToken value into the comment form.
AntiForgeryToken() Generates a hidden form field (anti-forgery token) that is validated when the form is submitted. AntiForgeryToken(String) Obsolete. Generates a hidden form field (anti-forgery token) that is validated when the form is submitted.
If your MachineKey is set to AutoGenerate, then your verification tokens, etc won't survive an application restart - ASP.NET will generate a new key when it starts up, and then won't be able to decrypt the tokens correctly.
If you are seeing this a lot, I'd suggest:
1 The best way to do this is by having a loadbalanced application, which will require you to set a static MachineKey. Another option is to take the site down by placing a file named app_offline.htm
in the root of the site, which will take the site offline and display your message - at least the users will expect things to go wrong.
For now I've gone with a solution that scrubs the cookie if the exception is thrown. If the exception is thrown again I'll just let it happen as it was.
I won't mark this as 'the' answer for now in the hope that someone has a better answer.
public static class MyAntiForgeryExtensions { // Methods public static string MyAntiForgeryToken(this HtmlHelper helper) { return MyAntiForgeryToken(helper, null); } public static string MyAntiForgeryToken(this HtmlHelper helper, string salt) { string fragment; string path = helper.ViewContext.HttpContext.Request.ApplicationPath; try { fragment = helper.AntiForgeryToken(salt, null, path); } catch (HttpAntiForgeryException) { // okay, scrub the cookie and have another go. string cookieName = GetAntiForgeryTokenName(path); helper.ViewContext.HttpContext.Request.Cookies.Remove(cookieName); fragment = helper.AntiForgeryToken(salt, null, path); } return fragment; } #region AntiForgeryData code that shouldn't be sealed // Copied from AntiForgeryData since they aren't accessible. internal static string GetAntiForgeryTokenName(string appPath) { if (String.IsNullOrEmpty(appPath)) { return "__RequestVerificationToken"; } else { return "__RequestVerificationToken_" + Base64EncodeForCookieName(appPath); } } private static string Base64EncodeForCookieName(string s) { byte[] rawBytes = Encoding.UTF8.GetBytes(s); string base64String = Convert.ToBase64String(rawBytes); // replace base64-specific characters with characters that are safe for a cookie name return base64String.Replace('+', '.').Replace('/', '-').Replace('=', '_'); } #endregion }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With