Troubleshooting anti-forgery token problems
Asked Answered
S

11

36

I have a form post that consistently gives me an anti-forgery token error.

Here is my form:

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
    @Html.EditorFor(m => m.Email)
    @Html.EditorFor(m => m.Birthday)
    <p>
        <input type="submit" id="Go" value="Go" />
    </p>
}

Here is my action method:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Join(JoinViewModel model)
{
    //a bunch of stuff here but it doesn't matter because it's not making it here
}

Here is the machineKey in web.config:

<system.web>
  <machineKey validationKey="mykey" decryptionKey="myotherkey" validation="SHA1" decryption="AES" />
</system.web>

And here is the error I get:

A required anti-forgery token was not supplied or was invalid.

I've read that changing users on the HttpContext will invalidate the token, but this isn't happening here. The HttpGet on my Join action just returns the view:

[HttpGet]
public ActionResult Join()
{
    return this.View();
}

So I'm not sure what's going on. I've searched around, and everything seems to suggest that it's either the machineKey changing (app cycles) or the user/session changing.

What else could be going on? How can I troubleshoot this?

Sanburn answered 24/4, 2011 at 0:0 Comment(3)
This page has been viewed nearly 4000 times in eighteen months and nobody else figured out that all you need to do to duplicate this is double click on the login button?Gibbous
possible duplicate of Prevent double submission of forms in jQueryOrganon
Double posting is one way to trigger an anti-forgery token exception. As you can see from the code below, there are many different scenarios that can throw this, and in my case specifically, it had nothing to do with double posting.Sanburn
S
19

After help from Adam, I get the MVC source added to my project, and was able to see there are many cases that result in the same error.

Here is the method used to validate the anti forgery token:

    public void Validate(HttpContextBase context, string salt) {
        Debug.Assert(context != null);

        string fieldName = AntiForgeryData.GetAntiForgeryTokenName(null);
        string cookieName = AntiForgeryData.GetAntiForgeryTokenName(context.Request.ApplicationPath);

        HttpCookie cookie = context.Request.Cookies[cookieName];
        if (cookie == null || String.IsNullOrEmpty(cookie.Value)) {
            // error: cookie token is missing
            throw CreateValidationException();
        }
        AntiForgeryData cookieToken = Serializer.Deserialize(cookie.Value);

        string formValue = context.Request.Form[fieldName];
        if (String.IsNullOrEmpty(formValue)) {
            // error: form token is missing
            throw CreateValidationException();
        }
        AntiForgeryData formToken = Serializer.Deserialize(formValue);

        if (!String.Equals(cookieToken.Value, formToken.Value, StringComparison.Ordinal)) {
            // error: form token does not match cookie token
            throw CreateValidationException();
        }

        string currentUsername = AntiForgeryData.GetUsername(context.User);
        if (!String.Equals(formToken.Username, currentUsername, StringComparison.OrdinalIgnoreCase)) {
            // error: form token is not valid for this user
            // (don't care about cookie token)
            throw CreateValidationException();
        }

        if (!String.Equals(salt ?? String.Empty, formToken.Salt, StringComparison.Ordinal)) {
            // error: custom validation failed
            throw CreateValidationException();
        }
    }

My problem was that condition where it compares the Identity user name with the form token's user name. In my case, I didn't have the user name set (one was null, the other was an empty string).

While I doubt many will run into this same scenario, hopefully others will find it useful seeing the underlying conditions that are being checked.

Sanburn answered 25/4, 2011 at 16:22 Comment(5)
btw.. How did you end up with that scenario of an empty string and null username?Gangrene
Kind of an odd situation, which is why it was so hard to troubleshoot. But I had my custom Identity set to use the user email as the UserName. Later, I made it where the email wasn't required when signing up (Twitter API doesn't provide email), so in those cases, email is null. I think the form username was empty, and comparing that w/ null Identity.UserName, it failed. I had forgotten that the Identity depended on email, so now I just made it use the user ID instead. Thanks again for your help.Sanburn
I ran into the same exact problem... Using Windows Identity Foundation... and blogged about it. Thanks a lot for the hint on where to start looking... I would say it's a bug in MVC (although a suttle one...) erikbra.wordpress.com/2011/08/10/…Columbia
Has this validate function, specifically the exceptions that are thrown, been updated in MVC5 to have more descriptive errors to help the admins know where the root cause is coming from? Having a generic response for 5 different scenarios is NOT good architecture/coding in my eyes.Steelhead
Which class contains this method?Induct
G
34

I don't know if you mean you are able to get the error on demand - or you're seeing it in your logs but in any case here's a way to guarantee an antiforgery token error.

Wait for it...

  • Make sure you're logged out, then enter your login
  • Double click on the login button
  • You'll get :

The provided anti-forgery token was meant for user "", but the current user is "[email protected]".

(For now I'm going to assume that this exact error message changed in MVC4 and that this is essentially the same message you're getting).

There's a lot of people out there that still double click on everything - this is bad! I just figured this out after just waking up so how this got through testing I really don't know. You don't even have to double click - I've got this error myself when I click a second time if the button is unresponsive.

I just removed the validation attribute. My site is always SSL and I'm not overly concerned about the risk. I just need it to work right now. Another solution would be disabling the button with javascript.

This can be duplicated on the MVC4 initial install template.

Gibbous answered 23/2, 2013 at 21:40 Comment(7)
If anyone finds a Connect issue related to this issue - please post it in the commentsGibbous
I prevent this type of issue using this code $('#loginForm form').submit(function () { $(this).find('input[type=submit]').attr("disabled","disabled"); });Organon
@Bohdan you should make your comment an answer. I knew what was causing my problems (users double clicking) but was looking for a quick fix. Your JS works a treatHighbinder
@WillD not fixed or modified in MVC 5? I've upgraded but haven't had chance to checkGibbous
@Gibbous I'm using MVC5 and double click still throws this errorHighbinder
I "hope" this is the issue that I'm having. Kind of sad that it's still a problem in MVC5...Steelhead
@Gibbous SSL doesn't help with CSRF attacks, according to Microsoft: learn.microsoft.com/en-us/aspnet/web-api/overview/security/… SSL does not prevent a CSRF attack, because the malicious site can send an "https://" request_Muddleheaded
S
19

After help from Adam, I get the MVC source added to my project, and was able to see there are many cases that result in the same error.

Here is the method used to validate the anti forgery token:

    public void Validate(HttpContextBase context, string salt) {
        Debug.Assert(context != null);

        string fieldName = AntiForgeryData.GetAntiForgeryTokenName(null);
        string cookieName = AntiForgeryData.GetAntiForgeryTokenName(context.Request.ApplicationPath);

        HttpCookie cookie = context.Request.Cookies[cookieName];
        if (cookie == null || String.IsNullOrEmpty(cookie.Value)) {
            // error: cookie token is missing
            throw CreateValidationException();
        }
        AntiForgeryData cookieToken = Serializer.Deserialize(cookie.Value);

        string formValue = context.Request.Form[fieldName];
        if (String.IsNullOrEmpty(formValue)) {
            // error: form token is missing
            throw CreateValidationException();
        }
        AntiForgeryData formToken = Serializer.Deserialize(formValue);

        if (!String.Equals(cookieToken.Value, formToken.Value, StringComparison.Ordinal)) {
            // error: form token does not match cookie token
            throw CreateValidationException();
        }

        string currentUsername = AntiForgeryData.GetUsername(context.User);
        if (!String.Equals(formToken.Username, currentUsername, StringComparison.OrdinalIgnoreCase)) {
            // error: form token is not valid for this user
            // (don't care about cookie token)
            throw CreateValidationException();
        }

        if (!String.Equals(salt ?? String.Empty, formToken.Salt, StringComparison.Ordinal)) {
            // error: custom validation failed
            throw CreateValidationException();
        }
    }

My problem was that condition where it compares the Identity user name with the form token's user name. In my case, I didn't have the user name set (one was null, the other was an empty string).

While I doubt many will run into this same scenario, hopefully others will find it useful seeing the underlying conditions that are being checked.

Sanburn answered 25/4, 2011 at 16:22 Comment(5)
btw.. How did you end up with that scenario of an empty string and null username?Gangrene
Kind of an odd situation, which is why it was so hard to troubleshoot. But I had my custom Identity set to use the user email as the UserName. Later, I made it where the email wasn't required when signing up (Twitter API doesn't provide email), so in those cases, email is null. I think the form username was empty, and comparing that w/ null Identity.UserName, it failed. I had forgotten that the Identity depended on email, so now I just made it use the user ID instead. Thanks again for your help.Sanburn
I ran into the same exact problem... Using Windows Identity Foundation... and blogged about it. Thanks a lot for the hint on where to start looking... I would say it's a bug in MVC (although a suttle one...) erikbra.wordpress.com/2011/08/10/…Columbia
Has this validate function, specifically the exceptions that are thrown, been updated in MVC5 to have more descriptive errors to help the admins know where the root cause is coming from? Having a generic response for 5 different scenarios is NOT good architecture/coding in my eyes.Steelhead
Which class contains this method?Induct
C
9

AntiForgeryToken also checks your logged in user credentials haven't changed – these are also encrypted in the cookie. You can turn this off by setting AntiForgeryConfig.SuppressIdentityHeuristicChecks = true in the global.asax.cs file.

Cryostat answered 10/2, 2014 at 14:31 Comment(2)
Not a solution for me ... I tried it but it doesn't workAntevert
What are the security implications of setting this?Lengthen
O
8

You should prevent double form submission. I prevent this type of issue using code like this:

$('#loginForm').on('submit',function(e){
    var $form = $(this);

    if (!$form.data('submitted') && $form.valid()) {
      // mark it so that the next submit can be ignored
      $form.data('submitted', true);
      return;
    }

    // form is invalid or previously submitted - skip submit
    e.preventDefault();
});

or

$('#loginForm').submit(function () {
    $(this).find(':submit').attr('disabled', 'disabled'); 
});
Organon answered 19/6, 2014 at 12:2 Comment(4)
This is bad: if you have client-side javascript form validation, your code will disable the submit button even if the form is invalid meaning users can't fix the client-side errors and resubmit.Latterll
You can use the jQuery .valid() method to check if the form is valid before disabling the submit button.Latterll
Saw this answer after I wrote my JS version. This should be better: $("form").on("submit", function (e) { var self = $(this); if (!self[0].checkValidity() || self.data("submitted")) { e.preventDefault(); return; } self.data("submitted", true); });Mudslinging
This answer seemed to work for me until I realized with client side validation, the form what considered submitted even if a field was in error and nothing was truly submitted.Kelm
G
3

Are you on one server or a web farm? If a single server, comment out your machineKey element in your web.config and try again as a base starting point. Any change? Also - can you think of any reasons your cookies would be getting cleared or expiring - they are required for this to work properly as well.

Gangrene answered 24/4, 2011 at 1:21 Comment(12)
I run this on a Windows virtual from a Mac. But I'm staying within the virtual. The server is a development server started by Visual Studio (2010). I'm also using a hosts header record to route a "real" URL to my local box (i.e. so that the live URL points to my local). Regarding the machineKey, I actually didn't have this there at first, and then added it after I started having problems. So it's broken both ways.Sanburn
Check that the cookie is valid and making it to the server for the correct domain. Use fiddler to check the cookie domain is correct. Are you staying within this host address the entire time (and never switching back to localhost instead of yoursite.com- not even for any ajax functions). The token is not session dependent so a session ending shouldn't matter. You could get the mvc source and step into the method to validate where it's failing.Gangrene
Thanks Adam, I will do that. I will say that this involves some interaction with Twitter. Basically, I have (me) TwitterAuthorize -> (twitter) Twitter's authorization -> (me) TwitterCallback -> (me) Join. But the page that calls this page is on my server, so I didn't think this remote Twitter call was a factor.Sanburn
Update - it looks like now the problem is specific to my second test account. For some reason, my first test account can post forms just fine, but my second account consistently fails the anti forgery token validation. Inspecting the cookies, the cookies on both accounts seem pretty much the same, the same number of cookies, same names, and same domains. Different values, of course.Sanburn
Doh - next step (for me at least) would be MVC debugging into the source to see the failure and why it's failing, values, etc. The code is fairly straight forward for it - check out this post on getting it setup: weblogs.asp.net/gunnarpeipman/archive/2010/07/04/…Gangrene
Hmm, next problem is how to debug attributes? I've always had a problem stepping into attributes. Usually, like with action filters, I can set a breakpoint. But there's no way to set a breakpoint on an attribute and step into it, is there?Sanburn
once you can debug the source as per the article just set the breakpoint in the mvc code for the attribute - should work just fine I debugged an attribute last night. If the breakpoint doesnt get hit make sure the symbols loaded - check my blog entry at: completedevelopment.blogspot.com/2009/11/… Section: "Then there is the method where you don't need to copy the pdb file."Gangrene
Sorry, but it's not obvious how to get to the source of the attribute. I followed the article, and it looks like it downloaded the source, but doing a "goto definition" (which usually takes me to the source of a class) just takes me to the object browser.Sanburn
It looks like the System.Web.Mvc.dll symbols aren't getting loaded. Inspecting the source folder, it pulled most framework DLLs, but not that one (even though I know I saw it come up when it was pulling symbols down from the server).Sanburn
Thanks for your help, Adam, but this is snowballing into a host of other problems. I've tried adding the source System.Web.Mvc project from Codeplex, and that's throwing all sorts of conflicting errors. And System.Web.Mvc won't load from the Microsoft Symbols Server. Looks like I'm back to having to figure it out without being able to debug.Sanburn
email me at adam.tuliper @ g mail . com - I'll see if I cant get the debugging steps for MVC setup.you should just need to set the symbols and then point to the source on your system. you can also go to debug-modules I think and see if they loaded or not to be sure.Gangrene
Thanks, Adam, I finally got it working. I had to combine steps from a few different steps. I ended up downloading the source, adding 5 MVC projects to my solution, changing the references, removing the strong name info from my web configs, and that finally got it to where I could debug. Doing that, I see exactly what's going on now, and will post that as an answer. Thanks again for all your help.Sanburn
A
3

I just experienced an issue where @Html.AntiForgeryToken() was being called twice so the Anti-forger token was get screwed up in the HTTP Post payload.

Ardenardency answered 13/12, 2012 at 23:34 Comment(0)
M
2

Is necessary check the form is it valid, before disable submit button.

<script type="text/javascript">
//prevent double form submission
$('form', '#loginForm').submit(function () {
    if ($(this).valid()) {
        $(this).find(':submit').attr('disabled', 'disabled');
    }
});

Mariande answered 2/10, 2015 at 19:31 Comment(0)
B
1

Another possible thing to check which has caused this error for me: I had two @Html.AntiForgeryToken() in one of my forms.

Once that was removed problem went away.

Blacksmith answered 1/11, 2014 at 18:18 Comment(0)
E
1

I also had this very same issue when I had to migrate my application to a new machine. I couldn't understand why this error suddenly manifested but felt certain it had something to do with the migration so started to investigate the registration of the database through aspnet_reqsql, when this was eliminated I realized it must be to do with the registration of the application and sure enough I found the answer in a similar place. I also on my journey discovered there are 2 ways to resolve this.

  1. ASP.NET automatically generates a cryptography key for each application and stores the key in the HKCU registry hive. When the application is migrated or accessed on a server farm these keys do not match, so method one is to add a unique machine key to the web.config. The machine key can be generated from IIS management console and added to the section of your web.config

    <machineKey validationKey="DEBE0EEF2A779A4CAAC54EA51B9ACCDED506DA2A4BEBA88FA23AD8E7399C4A8B93A006ACC1D7FEAEE56A5571B5AB6D74819CFADB522FEEB101B4D0F97F4E91" decryptionKey="7B1EF067E9C561EC2F4695578295EDD5EC454F0F61DBFDDADC2900B01A53D4" validation="SHA1" decryption="AES" />

  2. The second method is to grant access to the HKCU registry for the worker process through the appPool using AspNet_RegIIS and the switch -ga or for all apps using -i

aspnet_regiis -ga "IIS APPPOOL\app-pool-name"

Whichever method you choose should resolve this issue, but for my mind the most robust way for future migrations and server changes is going to be the unique key in the web.config, bearing in mind that this will cause the app to override the HKCU registry hive and keep you application running.

Endoderm answered 22/4, 2016 at 8:32 Comment(0)
H
1

I just had a similar problem. I got:

The required anti-forgery form field "__RequestVerificationToken" is not present. 

What's of interest is that I tried debugging it and saw the token in both places I expected to find it in the controller

var formField = HttpContext.Request.Params["__RequestVerificationToken"];
var cookie = System.Web.HttpContext.Current.Request.Cookies["__RequestVerificationToken"].Value;

In reality I should have been looking here:

var formField = HttpContext.Request.Form["__RequestVerificationToken"];

As Params contains more than just the form fields, it contains the QueryString, Form, Cookies and ServerVariables.

Once that red herring was out of the way I found the AntiForgeryToken was in the wrong form!

Hectorhecuba answered 1/6, 2018 at 12:52 Comment(0)
E
0

Also hit the A required anti-forgery token was not supplied or was invalid. message today.

Issue was related to https vs http and the actual root cause was me not realizing that i was starting the api-application using the http only configuration and the web-spa using https.

Hope this will help somebody to not waste 4 hours of lifetime because of a user-error

Elwin answered 20/2, 2023 at 23:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.