Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I solve an AntiForgeryToken exception that occurs after an iisreset in my ASP.Net MVC app?

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) 
like image 712
Colin Newell Avatar asked Feb 05 '10 10:02

Colin Newell


People also ask

How do I refresh AntiForgeryToken?

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.

What is AntiForgeryToken C#?

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.


2 Answers

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. Configuring a static MachineKey (you should be able to do this at the application level), see "How to: Configure a MachineKey" for more information
  2. Try not to perform IIS Resets when the site is being used1

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.

like image 133
Zhaph - Ben Duguid Avatar answered Oct 06 '22 10:10

Zhaph - Ben Duguid


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 } 
like image 33
Colin Newell Avatar answered Oct 06 '22 11:10

Colin Newell