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

6

45

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)
Margalit answered 5/2, 2010 at 10:45 Comment(1)
See also: #1360578Chitwood
D
45

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.

Doc answered 5/2, 2010 at 13:39 Comment(3)
Ahh, that might well be the problem. I'll give it a try and assuming it works I'll mark this as the answer. Not doing the iisresets is a good idea but I'm just trying to make sure that it works as reliably as possible in the real world.Margalit
Here are some very good articles on this subject adam.kahtava.com/journal/2009/11/23/… adam.kahtava.com/journal/2009/11/25/…Lungi
I have proper machine key but sometimes I've got HttpAntiForgeryException. :( I don't know why. :/Osmen
M
15

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
}
Margalit answered 5/2, 2010 at 11:27 Comment(3)
I did the same thing. I think this is a big design flaw with the AntiForgeryToken method, and like your comments suggest, it's even more frustrating that the MVC code is internal/sealed, because it prevents us from easily fixing it!Chamomile
On a related note, it's also frustrating that the ValidateAntiForgeryToken attribute is sealed, because it has a few shortcomings too. I actually ended up grabbing the VAFT source, un-sealing it, and creating my own subclass that can be applied to an entire Controller and supports JSON requests.Chamomile
one thing I would change for MVC3 support is that your static helper extension methods should return MvcHtmlString, because helper.AntiForgeryToken(string, string, string) returns that type instead of a plain string.Interleave
S
11

i had this issue and to fix what you need to do is add an explicit machine key in your web-config...

<machineKey validationKey="D82960E6B6E9B9029D4CAB2F597B5B4AF631E3C182670855D25FBDE1BFAFE19EFDE92ABBD1020FC1B2AE455D5B5F8D094325597CE1A7F8B15173407199C85A16" decryptionKey="577404C3A13F154908D7A5649EEC8D7C8A92C35A25A3EC078B426BB09D426A71" validation="SHA1" decryption="AES" /> 

ensure its placed in web.config within...

<system.web>
Splice answered 21/6, 2013 at 9:59 Comment(2)
Given link is not working. Please use below URL [allkeysgenerator.com/Random/ASP-Net-MachineKey-Generator.aspx]Vanthe
I may be paranoid , but i would not use any internet website to generate my keys for me. Later versions of IIS can do it, and you can also do it using the .NET system class RNGCryptoServiceProvider .Partheniaparthenocarpy
D
7

You can add AntiForgeryConfig.SuppressIdentityHeuristicChecks = true; into global.asax

protected void Application_Start() {
    AntiForgeryConfig.SuppressIdentityHeuristicChecks = true;
}
Dust answered 6/12, 2013 at 10:29 Comment(2)
What does that do, exactly?Therewithal
@TimothyGroote from asp.net/mvc/overview/security/…: "A Boolean that dictates whether the anti-XSRF system should deactivate its support for claims-based identities. If this value is true, the system will assume that IIdentity.Name is appropriate for use as a unique per-user identifier and will not try to special-case IClaimsIdentity or ClClaimsIdentity as described in the WIF / ACS / claims-based authentication section. The default value is false." This requires IIdentity.Name to be a sufficiently unique identifier to be secure.Etam
P
4

If I do an iisreset on my web server and a user continues with their session they get bounced to a login page.

There's no reason an iisreset to bring the user to the login page. If you use cookies to track authentication information and have a stateless application, the user should stay authenticated even after a server reboot (of course if a request is made during the reset it will fail).

Polygamist answered 5/2, 2010 at 10:50 Comment(3)
That's technically true and something I really ought to solve but that's a seperate issue. The fact that I get the exception when I try to put the token on the page is the problem.Margalit
@Darin - persistent logins and the anti-forgery mechanism are reliant on the <machineKey/> values being consistent across IISResets. If they're set to AutoGenerate then the keys change and the cookie will no longer be valid.Utrillo
Yes, this causes significant problems in a shared hosting environment that's heavily load balanced. I did however, find a solution to the "Keep me logged in" issue, and that was to place the machineKey in my Web.config - this fixed it nicely.Enswathe
R
4

Actually I found this to work in my logon action:

    public ActionResult LogOn()
    {
        formsAuthentication.SignOut();

        Response.Cookies.Clear();

        Session[SessionKeys.USER_SESSION_KEY] = null;
        Session.Clear();
        Session.Abandon();


        return View();
    }

The important part was : Response.Cookies.Clear();

Rotter answered 27/3, 2010 at 18:26 Comment(3)
That half works. Zaph's point about the machine keys works better though because it prevents you getting bounced to the login page after the iisreset. It also prevents the cookie from becoming invalid because of an iisreset.Margalit
I also have a static Machine key but was having the same problem.Rotter
Trying to log in when already logged in should not be punished by logging the user out. In case the user unknowingly has multiple windows open and accidentally acts on a stale login page, the user risks losing work / state in the other windows. A better solution here is to forward them to the login screen, like Zia forwards them to an antiforgery page.Conformance

© 2022 - 2024 — McMap. All rights reserved.