Validating .NET MVC 4 anti forgery tokens in ajax requests

Posted on Updated on

CSRF (Cross-Site Request Forgery) is an attack against a website “whereby unauthorized commands are transmitted from a user that the website trusts.” [Wikipedia]. Protection against this attack is essential for any modern web application.

In the case of .NET MVC, Microsoft have implemented an easy-to-use protection method against CSRF. There’s an attribute in the MVC framework that you can put on your controller actions: ValidateAntiForgeryToken. This works well, but it has a few disadvantages:

  1. You must manually decorate all your post actions with the attribute.
    It’s easy to forget to do this, so it’s preferable to decorate your entire controller with the attribute (or better yet, a base controller that your whole application uses). Unfortunately, this doesn’t work with the standard ValidateAntiForgeryToken attribute, as this causes all your GET actions to be validated as well (and they will always blow up, as the client doesn’t send any form data with a GET request).
  2. It doesn’t work with ajax posts.
    The standard attribute doesn’t work with ajax because it inspects the Request.Form collection when looking for the token field. When you’re making ajax posts this form is always empty. This is the reason I originally started looking for alternative implementations of the anti forgery token validation.

Fortunately, a solution wasn’t too hard to implement myself. I wrote my own attribute that can a) decorate a controller class and will only validate that class’s POST actions, and b) will work with ajax posts by allowing you to put the anti forgery token in the headers for the request. The attribute class looks like this:

    [AttributeUsage(AttributeTargets.Class)]
    public class ValidateAntiForgeryTokenOnAllPosts : AuthorizeAttribute
    {
        public override void OnAuthorization( AuthorizationContext filterContext )
        {
            var request = filterContext.HttpContext.Request;

            //  Only validate POSTs
            if (request.HttpMethod == WebRequestMethods.Http.Post)
            {
                //  Ajax POSTs and normal form posts have to be treated differently when it comes
                //  to validating the AntiForgeryToken
                if (request.IsAjaxRequest())
                {
                    var antiForgeryCookie = request.Cookies[AntiForgeryConfig.CookieName];

                    var cookieValue = antiForgeryCookie != null 
                        ? antiForgeryCookie.Value 
                        : null;

                    AntiForgery.Validate(cookieValue, request.Headers["__RequestVerificationToken"]);
                }
                else
                {
                    new ValidateAntiForgeryTokenAttribute()
                        .OnAuthorization(filterContext);
                }
            }
        }
    }

Your client-side Javascript then looks something like this:

var token = $('[name=__RequestVerificationToken]').val();

$.ajax({
    type: 'POST',
    url: '/Home/Ajax',
    cache: false,
    headers: { '__RequestVerificationToken': token },
    contentType: 'application/json; charset=utf-8',
    data: { title: 'This is my title', contents: 'These are my contents' },
    success: function () {
        ...
    },
    error: function () {
        ...
    }
});

The important part of the JS above is the headers: { "__RequestVerificationToken": token }. This means that you can decorate your base controller with this new ValidateAntiForgeryTokenOnAllPosts attribute, and then all forms in your application will no longer work until you put @Html.AntiForgeryToken() in your form.

46 thoughts on “Validating .NET MVC 4 anti forgery tokens in ajax requests

    JanB said:
    2013-02-20 at 02:05

    hey thanks for this. help me lot. cheers

    Drewsonian said:
    2013-03-08 at 16:37

    This is great and EXACTLY what I needed! I combined an approach I found here http://stackoverflow.com/questions/14527360/can-i-set-a-global-header-for-all-ajax-requests
    to make all of my ajax operations automatically add the needed headers.

    $.ajaxSetup({
    ‘beforeSend’: function (xhr) {
    securityToken = $(‘[name=__RequestVerificationToken]’).val();
    xhr.setRequestHeader(“__RequestVerificationToken”, securityToken);
    }
    });

    Chris Schnyder said:
    2013-07-10 at 14:08

    This is brilliant. I’m on a tight deadline for an initial release, but didn’t want to leave any holes in the app that might be abused. Your code here saved me some hassle. Thanks!

    Tom said:
    2013-07-11 at 18:27

    Out of all that Google recommends, this was and is the cleanest and most appropriate solution. Worked easily and perfectly. Quick, run and post this on Stack…

    hades200082 said:
    2013-08-28 at 15:57

    How does this work in ajax aplpications where we may not see a page refresh between ajax requests?

    Doesn’t it need some way of getting a new anti-forgery token after using one?

    Pushkar said:
    2013-09-09 at 07:09

    Complex problems have simplest of Solutions.. Cheers 🙂

    Mohammed ElSayed said:
    2013-12-23 at 10:25

    Super like, I’ve used your attribute and it works like a charm 🙂

    aaditya parcha said:
    2013-12-31 at 07:53

    Hi ,
    thanks for the nice post

    I found the token generated in html form keep changing every time a new form is rendered. I want to know how these token is generated?
    And there is another function, that is “salt” for the antiForgeryToken, but i really know what this used for, even through we don’t use “salt” to generate the token, the token will changes all the time, so why we have such function ?

    Thanks in advance

      richiban responded:
      2013-12-31 at 11:30

      The token is generated randomly on every page load – and it needs to be. If you used the same token every time then an attacker could very quickly learn what that token is and then it wouldn’t be any use at all.

    JS said:
    2014-01-14 at 15:26

    During server side token validation on an Ajax post, I get the following error:-

    The anti-forgery token could not be decrypted. If this application is hosted by a Web Farm or cluster, ensure that all machines are running the same version of ASP.NET Web Pages and that the configuration specifies explicit encryption and validation keys. AutoGenerate cannot be used in a cluster.

    AntiForgery.Validate(cookieValue, HttpContext.Current.Request.Headers[“__RequestVerificationToken”]);

    I have a valid machinekey element defined in the web.config, which is being used to encrypt the token. Any help is appreciated, thanks,

    Sean said:
    2014-02-06 at 22:29

    Can’t seem to get this to work at the Method level. Doesn’t run the attribute. Anyway to do this at the Method level and not the Class level?

      richiban responded:
      2014-02-15 at 11:02

      If you want to decorate each method individually then you don’t need to use what I wrote – the stuff that MVC has out of the box will do you just fine. Make use of the ValidateAntiForgeryTokenAttribute on each of your action methods.

    Ben said:
    2014-02-15 at 03:36

    Richiban,

    I think your solution is better than the one I found at ASP.,net page and the code is definitely better. I had no problem implementing the Java Script and copying the class in the controller, but that is how far I could go. Now, what else needs to be done in the controller for all this to work? What attribute do I need to decorate the ActionResult that is been called by AJAX / Post after the form is submitted ? The way it is now, if I remove the [ValidateAntiForgeryToken] it works, if not it trows an error.. Thanks for any answer. I’m kind of new to MVC / JavaScript

    Ben said:
    2014-02-15 at 03:43

    Richiban,

    To better explain my question above, if I decorate with

    [ValidateAntiForgeryTokenOnAllPosts] it tells the following:

    Attribute ‘ValidateAntiForgeryTokenOnAllPosts’ is not valid on this declaration type. It is only valid on ‘class’ declarations.

    Do I need to put your code in a base class and derive the controller from there?

    Thanks again

      richiban responded:
      2014-02-15 at 11:09

      It sounds like you’re trying to decorate your action method with the Attribute. It should go on your controller – that’s what the error you’re getting says.

        Ben said:
        2014-02-15 at 15:43

        I use an Ajax call to refresh a partial view, but it doesn’t work (as expected) if the “[ValidateAntiForgeryToken]” is decorating the ActionResult in the controller. So, I remove it and it works, Then, I place your class in the same controller, with the attribute “[AttributeUsage(AttributeTargets.Class)]”, exactly like in your post. Everything works so far so good. But then, if I remove the “@Html.AntiForgeryToken();” from the View, it still works without throwing an exception for the invalid (or missing) token. I understood what is the purpose of your code, but I’m basically trying to use it to solve the Ajax call issue with tokens, when the dataType is not html. For what I understood, all I need to do is to place your code in the controller and that is it?
        Do I need to decorate the ActionResult with any attribute other than [HttpPost]? What am I missing or not doing correctly?

    a hole said:
    2014-02-15 at 17:30

    Html.AntiForgeryToken has been deprecated and does not for mvc4 rofl

    Neha said:
    2014-03-03 at 22:51

    Awesome. Really I tired so many methods out there. But none of them explained it so simply like you did. Very good article. Good job.

    spidergeuse said:
    2014-03-09 at 00:57

    Nice! I will keep you idea for use some time later.

    In my MVC4 applicaiton, I used the ValidateAntiForgeryToken attribute and then the snippet below (from stack overflow), which append the token to the post data of all my ajax posts, and everything works just fine and is clean/neat too.

    var token = $(‘input[name=”__RequestVerificationToken”]’).val();
    $.ajaxPrefilter(function (options, originalOptions) {
    if (options.type.toUpperCase() == “POST”) {
    options.data = $.param($.extend(originalOptions.data, { __RequestVerificationToken: token }));
    console.log(options)
    }
    });

    http://stackoverflow.com/questions/19788916/how-to-make-ajax-request-with-anti-forgery-token-in-mvc

    Georges Damien said:
    2014-03-11 at 16:48

    Thanks. the way you did this is very simple and interesting.

    John Mc Avinue said:
    2014-04-14 at 13:24

    Thank you, worked a treat!

    PJ said:
    2014-04-24 at 15:08

    Hi, I have tried the following but the token is not being added.
    I get an error saying token is not present and looking in fiddler I do not see it. I am missing something obvious?
    // add AntiForgeryToken to header
    var token = $(‘[name=__RequestVerificationToken]’).val();
    var headers = {};
    headers[“__RequestVerificationToken”] = token;

    $.ajax({
    type: “POST”,
    cache: false,
    headers: headers,
    url: “/TimeOn/Reject/?employeeId=” + employeeId + “&timesheetId=” + timesheetId + “&reason=” + $(refTextArea).val(),
    dataType: “html”,
    success: function (result) {
    window.location.reload();
    }
    });

      richiban responded:
      2014-04-24 at 15:12

      First we should check that you do have @Html.AntiForgeryToken() in your form somewhere?

        PJ said:
        2014-04-24 at 15:53

        thanks for the quick reply. my razor view does not have a form. a button is triggering the post.

        PJ said:
        2014-04-24 at 16:08

        again thanks, for now I added a dummy form to the page and put the token there…….got me over this hurdle

        richiban responded:
        2014-04-24 at 16:28

        Yes, you should really have a form in the view. A button on its own will still trigger a post, but the “form” is implicit. Without explicitly declaring a form yourself you will not be able to send any information in the post request except the value of the button that was clicked.

    PJ said:
    2014-04-28 at 09:02

    I would appreciate your opinion on the following…..as a global approach for my web app I was thinking of adding a form tag to my layout view and including the antiforgerytoken there. this means all views will have a token and I will not have to worry about including it each time.
    Is this a reasonable approach?
    Is there any technical or performance reason that this may fail?

    Thanks for your help.
    /pj

      richiban responded:
      2014-05-01 at 15:06

      You could probably make that work, but you’re going to run into problems sooner or later. You will, at some point, need a different form in one of your views and you might get undefined behaviour if you have a form within a form. To me it sounds like you’re not really doing MVC if you’re happy with having a form in the layout. Remember, in MVC you don’t detect “button clicks”, but rather you take the visitor to a URL to perform an action. So, if you have an “order” view and you want a “delete” button on that view, that button is actually just a link to /orders/delete/5911, for example.

    hardywang said:
    2014-05-01 at 14:45

    Does this solution work for Ajax.Begin form? Because what I noticed is that if I use Ajax.BeginForm then request.IsAjaxRequest() returns true, but $.ajaxSetup() does not seems to be invoked from broswer, thus the Http header never has the token value, and it fails all the time.

      richiban responded:
      2014-05-01 at 15:10

      I’m not too familiar with Ajax.BeginForm, as the Ajax code that I write is almost always by hand. From what you’ve said it looks like it won’t work. You probably want to use something such as $.post( "test.php", $( "#testform" ).serialize() ); from https://api.jquery.com/jQuery.post/ to do the posting. That way you have control over all aspects of what gets posted, to where, and with what extra data.

    Bob said:
    2014-07-08 at 11:33

    Truly brilliant!

    […] Based on code from @Richiban here […]

    Jose Rojas said:
    2014-10-22 at 19:43

    is this a class? AntiForgeryConfig.CookieName?? or where is this comming from?

    John M said:
    2015-07-29 at 05:58

    Hi,

    I am new in this. Can any one explain how to implement step by step this soluation.? I mean, do i need to create separeted class? What code do i need to add in controller? Sample working example will be fine if you can share.

    Thank you.

    Regards,
    John

    AJ said:
    2015-08-13 at 18:02

    Thanks a ton for this!!!

    AJ said:
    2015-08-13 at 18:02

    Reblogged this on CODEFLIX.

    Arthur said:
    2015-08-17 at 18:41

    Hi, I have implemented the entire code shown above. But when comes in the part that the Class “AntiForgery” will validate the two strings (cookieValue and header prop), the values of the two strings are different and the method returns an error: “The required anti-forgery form field “__RequestVerificationToken” is not present.”. Am I doing something wrong?

    business advice said:
    2016-06-17 at 00:22

    Just want to say your article is as astonishing. The clarity on your put up is
    just great and i can think you are a professional in this subject.

    Well together with your permission allow me to take hold of your feed to stay updated with approaching post.
    Thank you a million and please continue the enjoyable work.

    Jonas Kromwell said:
    2016-08-06 at 21:47

    The missing and importantly deciding point on getting this solution for me was the javascript token tag missing the quotes around the name.

    The code sample as given didn’t work:

    var token = $(‘[name=__RequestVerificationToken]’).val();

    This did:

    var token = $(‘[name=”__RequestVerificationToken”]’).val();

    I nearly gave up in exasperation on this like dozens of other promising solutions out there. This little detail made all the difference. Thanks.

      richiban responded:
      2016-08-08 at 08:55

      That’s strange Jonas, it worked for me (and continues to work) without the quotes in Chrome. What browser are you using? I’m glad you got to the bottom of it eventually.

        Jonas Kromwell said:
        2016-08-09 at 01:22

        It was Chrome. It looks to be updated as well. Not sure why the trouble but it’s a simple enough fix once uncovered.

Leave a comment