Monday, June 11, 2012

jQuery Ajax calls and the Html.AntiForgeryToken()


I have implemented in my app the mitigation to CSRF attacks following the informations that I have read on some blog post around the internet. In particular these post have been the driver of my implementation




Basically those articles and recommendations says that to prevent the CSRF attack anybody should implement the following code:



1) Add the [ValidateAntiForgeryToken] on every action that accept the POST Http verb




[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult SomeAction( SomeModel model ) {
}



2) Add the <%= Html.AntiForgeryToken() %> helper inside forms that submits data to the server




<div style="text-align:right; padding: 8px;">
<%= Html.AntiForgeryToken() %>
<input type="submit" id="btnSave" value="Save" />
</div>



Anyway in some parts of my app I am doing Ajax POSTs with jQuery to the server without having any form at all. This happens for example where I am letting the user to click on an image to do a specific action.



Suppose I have a table with a list of activities. I have an image on a column of the table that says "Mark activity as completed" and when the user click on that activity I am doing the Ajax POST as in the following sample:




$("a.markAsDone").click(function (event) {
event.preventDefault();
$.ajax({
type: "post",
dataType: "html",
url: $(this).attr("rel"),
data: {},
success: function (response) {
// ....
}
});
});



How can I use the <%= Html.AntiForgeryToken() %> in these cases? Should I include the helper call inside the data parameter of the Ajax call?



Sorry for the long post and thanks very much for helping out



EDIT :



As per jayrdub answer I have used in the following way




$("a.markAsDone").click(function (event) {
event.preventDefault();
$.ajax({
type: "post",
dataType: "html",
url: $(this).attr("rel"),
data: {
AddAntiForgeryToken({}),
id: parseInt($(this).attr("title"))
},
success: function (response) {
// ....
}
});
});


Source: Tips4all

6 comments:

  1. I use a simple js function like this

    AddAntiForgeryToken = function(data) {
    data.__RequestVerificationToken = $('#__AjaxAntiForgeryForm input[name=__RequestVerificationToken]').val();
    return data;
    };


    Since every form on a page will have the same value for the token, just put something like this in your top-most master page

    <%-- used for ajax in AddAntiForgeryToken() --%>
    <form id="__AjaxAntiForgeryForm" action="#" method="post"><%= Html.AntiForgeryToken()%></form>


    Then in your ajax call do (edited to match your second example)

    $.ajax({
    type: "post",
    dataType: "html",
    url: $(this).attr("rel"),
    data: AddAntiForgeryToken({ id: parseInt($(this).attr("title")) }),
    success: function (response) {
    // ....
    }
    });

    ReplyDelete
  2. You can do this also:

    $("a.markAsDone").click(function (event) {
    event.preventDefault();
    $.ajax({
    type: "post",
    dataType: "html",
    url: $(this).attr("rel"),
    data: $('<form>@Html.AntiForgeryToken()</form>').serialize(),
    success: function (response) {
    // ....
    }
    });


    });

    This is using Razor, but if you're using WebForms syntax you can just as well use <%= %> tags

    ReplyDelete
  3. i was just implementing this actual problem in my current project. i did it for all ajax-POSTs that needed an authenticated user.

    First off i decided to hook my jquery ajax calls so i do not to repeat myself too often. this javascript snippet ensures all ajax (post) calls will add my request validation token to the request. Note: the name __RequestVerificationToken is used by the .Net framework so i can utilize the standard Anti-CSRF features as shown below.

    $(document).ready(function () {
    securityToken = $('[name=__RequestVerificationToken]').val();
    $('body').bind('ajaxSend', function (elm, xhr, s) {
    if (s.type == 'POST' && typeof securityToken != 'undefined') {
    if (s.data.length > 0) {
    s.data += "&__RequestVerificationToken=" + encodeURIComponent(securityToken);
    }
    else {
    s.data = "__RequestVerificationToken=" + encodeURIComponent(securityToken);
    }
    }
    });
    });


    In your Views where you need the token to be available to the above javascript just use the common HTML-Helper. You can basically add this code whereever you want. I placed it within a if(Request.IsAuthenticated) statement:

    @Html.AntiForgeryToken() // you can provide a string as salt when needed which needs to match the one on the controller


    In your controller simply use the standard ASP.Net MVC Anti-CSRF mechanism. I did it like this (though i actually used Salt).

    [HttpPost]
    [Authorize]
    [ValidateAntiForgeryToken]
    public JsonResult SomeMethod(string param)
    {
    // do something
    return Json(true);
    }


    With Firebug or a similar tool you can easily see how your POST requests now have a __RequestVerificationToken parameter appended.

    ReplyDelete
  4. I think all you have to do is ensure that the "__RequestVerificationToken" input is included in the POST request. The other half of the information (i.e. the token in the user's cookie) is already sent automatically with an AJAX POST request.

    E.g.,

    $("a.markAsDone").click(function (event) {
    event.preventDefault();
    $.ajax({
    type: "post",
    dataType: "html",
    url: $(this).attr("rel"),
    data: {
    "__RequestVerificationToken":
    $("input[name=__RequestVerificationToken]").val()
    },
    success: function (response) {
    // ....
    }
    });
    });

    ReplyDelete
  5. AntiforgeryToken is still a pain, none of the examples above worked word for word for me. Too many for's there. So I combined them all. Need a @Html.AntiforgeryToken in a form hanging around iirc

    Solved as so:

    function Forgizzle(eggs) {
    eggs.__RequestVerificationToken = $($("input[name=__RequestVerificationToken]")[0]).val();
    return eggs;
    }

    $.ajax({
    url: url,
    type: 'post',
    data: Forgizzle({ id: id, sweets: milkway }),
    });


    When in doubt, add more $ signs

    ReplyDelete
  6. I aware it's been some time since this question was posted, but I found really useful resource, which discusses usage of AntiForgeryToken and makes it less troublesome to use. It also provides jquery plugin for easily including antiforgery token in AJAX calls:

    Anti-Forgery Request Recipes For ASP.NET MVC And AJAX

    I'm not contributing much, but maybe someone will find it useful.

    ReplyDelete