Jquery Ajax 调用和 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) {
            // ....
        }
    });
});

转载于:https://stackoverflow.com/questions/4074199/jquery-ajax-calls-and-the-html-antiforgerytoken

weixin_41568184
叼花硬汉 The David Hayden link now 404s, it appears that he's migrated his blog to a new CMS, but didn't migrate all the old content over.
7 年多之前 回复

15个回答

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) {
        // ....
    }
});
csdnceshi69
YaoRaoLov Why isn't it added to the header instead? Is there a preferable way?
2 年多之前 回复
csdnceshi64
游.程 I doubt it is required for it to work, the form is there to keep the html valid.
4 年多之前 回复
csdnceshi66
必承其重 | 欲带皇冠 on the page? In my case my page only lists a bunch of products in a grid, but I am doing the deleting with an AJAX request so need the token, but don't want to add an empty form to my page if not necessary.
4 年多之前 回复
csdnceshi64
游.程 true, depending on the outputcache configuration. Caching the same output for different users is definitely a common usage, but it is possible to have the outputcache keyed on the same user.
4 年多之前 回复
csdnceshi78
程序go this will not work with outputcache enabled on your page
4 年多之前 回复
csdnceshi75
衫裤跑路 thanks, it turns i misunderstood how those tokens work
4 年多之前 回复
csdnceshi64
游.程 the validation token should be the same for a user across all pages (it works in conjunction with a cookie that is set and stays the same). So it doesn't matter if there are multiple requests per page, it'll be the same if the base page reloaded anyway.
4 年多之前 回复
csdnceshi75
衫裤跑路 what happens when i make a second ajax request on the same page without reloading? wouldn't the token be invalid then?
4 年多之前 回复
weixin_41568134
MAO-EYE Be careful if you use output cache.
接近 6 年之前 回复
csdnceshi52
妄徒之命 How bad an idea would it be to use ajaxSend or override ajax to always augment data with the anti-forgery token? Maybe adding some check to make sure that the url is destined for your server.
大约 7 年之前 回复
csdnceshi74
7*4 Re the above comment, when I implemented it, my url already had parameters in it (e.g. /action/id=444), so I simply didn't pass anything to AddAntiForgeryToken({}) , and it worked fine for me.
大约 7 年之前 回复
csdnceshi64
游.程 That's just an example of how to merge the antiforgery token into whatever data object you want to send. Nothing special about it, it just matches the OP's example.
7 年多之前 回复
csdnceshi54
hurriedly% What's the purpose of passing the id to the AddAntiForgeryToken()
7 年多之前 回复
csdnceshi60
℡Wang Yan jay, nice one. useful generic implementation. been working on the edges of something (less nice) for quite some time.
9 年多之前 回复
csdnceshi73
喵-见缝插针 Ok. Thank you very much!!! It works seamlessly :)
接近 10 年之前 回复
csdnceshi64
游.程 yeah, what @jball said
接近 10 年之前 回复
csdnceshi76
斗士狗 put your custom data inside the call to AddAntiForgeryToken, like so: data: AddAntiForgeryToken({ id: parseInt($(this).attr("title")) }),
接近 10 年之前 回复
csdnceshi73
喵-见缝插针 thanks for your response! Where do I have to place the small js function? I have placed inside a js file of common function and either in the master page but Chrome give me an error when I use it. It says Unexpected token (. Please see my question edit to see how I am using it.
接近 10 年之前 回复
csdnceshi64
游.程 Yeah, that's nice since a lot of times you do ajax posts you don't even have a form on the page to get the token from
接近 10 年之前 回复
csdnceshi76
斗士狗 Nice, I like the encapsulation of the token fetching.
接近 10 年之前 回复

found this very clever idea from https://gist.github.com/scottrippey/3428114 for every $.ajax calls it modifies the request and add the token.

// Setup CSRF safety for AJAX:
$.ajaxPrefilter(function(options, originalOptions, jqXHR) {
    if (options.type.toUpperCase() === "POST") {
        // We need to add the verificationToken to all POSTs
        var token = $("input[name^=__RequestVerificationToken]").first();
        if (!token.length) return;

        var tokenName = token.attr("name");

        // If the data is JSON, then we need to put the token in the QueryString:
        if (options.contentType.indexOf('application/json') === 0) {
            // Add the token to the URL, because we can't add it to the JSON data:
            options.url += ((options.url.indexOf("?") === -1) ? "?" : "&") + token.serialize();
        } else if (typeof options.data === 'string' && options.data.indexOf(tokenName) === -1) {
            // Append to the data string:
            options.data += (options.data ? "&" : "") + token.serialize();
        }
    }
});

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) {
            // ....
        }
    });
});
csdnceshi74
7*4 If your html page doesn't have a server supplied AntiForgeryToken it's all moot anyhow. If it does (not sure how you're getting one in that case, but assuming you are), then the above would work just fine. If you're attempting to create a simply webpage that will post a request to a server expecting said token, and the server did not generate said page, then you're out of luck. That's essentially the point of the AntiForgeryToken...
接近 6 年之前 回复
csdnceshi64
游.程 How would I implement this if the AJAX function was in a .html page and not a Razor page?
接近 6 年之前 回复
csdnceshi51
旧行李 After many hours experimenting with jQuery AJAX posting from within an MVC (Razor) page this was the simplest answer of all that worked for me. Just include your own data fields (or the viewModel I suppose) after the token as a new piece of data (but within the original data object).
接近 7 年之前 回复

I know there are a lot of other answers, but this article is nice and concise and forces you to check all of your HttpPosts, not just some of them:

http://richiban.wordpress.com/2013/02/06/validating-net-mvc-4-anti-forgery-tokens-in-ajax-requests/

It uses HTTP headers instead of trying to modify the form collection.

Server

//make sure to add this to your global action filters
[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);
            }
        }
    }
}

Client

var token = $('[name=__RequestVerificationToken]').val();
var headers = {};
headers["__RequestVerificationToken"] = token;

$.ajax({
    type: 'POST',
    url: '/Home/Ajax',
    cache: false,
    headers: headers,
    contentType: 'application/json; charset=utf-8',
    data: { title: "This is my title", contents: "These are my contents" },
    success: function () {
        ...
    },
    error: function () {
        ...
    }
});
csdnceshi64
游.程 thanks Tim, it is an excellent idea, its frustrating when a link goes dead and the answer becomes worthless. I've started doing this on all my new answers.
大约 6 年之前 回复
csdnceshi65
larry*wei Great find. I edited your answer to include the code snippets so the answer stands on its own, but I hope people will read the rest of the article as well. This appears to be a very clean solution.
6 年多之前 回复
weixin_41568131
10.24 The attribute from the article you linked too combined with Bronx's response is the ultimate DRY solution to this problem.
6 年多之前 回复

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

Further to my comment against @JBall's answer that helped me along the way, this is the final answer that works for me. I'm using MVC and Razor and I'm submitting a form using jQuery AJAX so I can update a partial view with some new results and I didn't want to do a complete postback (and page flicker).

Add the @Html.AntiForgeryToken() inside the form as usual.

My AJAX submission button code (i.e. an onclick event) is:

//User clicks the SUBMIT button
$("#btnSubmit").click(function (event) {

//prevent this button submitting the form as we will do that via AJAX
event.preventDefault();

//Validate the form first
if (!$('#searchForm').validate().form()) {
    alert("Please correct the errors");
    return false;
}

//Get the entire form's data - including the antiforgerytoken
var allFormData = $("#searchForm").serialize();

// The actual POST can now take place with a validated form
$.ajax({
    type: "POST",
    async: false,
    url: "/Home/SearchAjax",
    data: allFormData,
    dataType: "html",
    success: function (data) {
        $('#gridView').html(data);
        $('#TestGrid').jqGrid('setGridParam', { url: '@Url.Action("GetDetails", "Home", Model)', datatype: "json", page: 1 }).trigger('reloadGrid');
    }
});

I've left the "success" action in as it shows how the partial view is being updated that contains an MvcJqGrid and how it's being refreshed (very powerful jqGrid grid and this is a brilliant MVC wrapper for it).

My controller method looks like this:

    //Ajax SUBMIT method
    [ValidateAntiForgeryToken]
    public ActionResult SearchAjax(EstateOutlet_D model) 
    {
        return View("_Grid", model);
    }

I have to admit to not being a fan of POSTing an entire form's data as a Model but if you need to do it then this is one way that works. MVC just makes the data binding too easy so rather than subitting 16 individual values (or a weakly-typed FormCollection) this is OK, I guess. If you know better please let me know as I want to produce robust MVC C# code.

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 () {
    var 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.

weixin_41568183
零零乙 good solution. thx.
大约 8 年之前 回复

Don't use Html.AntiForgeryToken. Instead, use AntiForgery.GetTokens and AntiForgery.Validate from Web API as described in Preventing Cross-Site Request Forgery (CSRF) Attacks.

csdnceshi53
Lotus@ It certainly does not require a form. You just need to parse the DOM for it by name. Using jquery, I can add it inside my data object via data { __RequestVerificationToken: $("input[name=__RequestVerificationToken]").val() }
大约 4 年之前 回复
csdnceshi59
ℙℕℤℝ thx! I had to change it a bit to make it work for an MVC5 controller, but this was the solution
接近 6 年之前 回复
csdnceshi58
Didn"t forge There's nothing inherently wrong with Html.AntiForgeryToken, but it has downsides: requires a form, requires jQuery, and assumes undocumented Html.AntiForgeryToken implementation details. Still, it's fine in many contexts. My statement "Don't use Html.AntiForgeryToken" probably comes off too strong. My meaning is that it's not intended to be used with Web API, whereas the more flexible AntiForgery.GetTokens is.
6 年多之前 回复
csdnceshi69
YaoRaoLov Brey Can you please elaborate on why we shouldn't use it?
6 年多之前 回复
csdnceshi61
derek5. For controller action methods that model bind a server model type to the posted AJAX JSON, having the content type as "application/json" is required for the proper model binder to be used. Unfortunately, this precludes using form data, required by the [ValidateAntiForgeryToken] attribute, so your method is the only way I could find to make it work. My only question is, does it still work in a web farm or multiple Azure web role instances? Do you @Edward, or anyone else know if this is a problem?
大约 7 年之前 回复

1.Define Function to get Token from server

@function
{

        public string TokenHeaderValue()
        {
            string cookieToken, formToken;
            AntiForgery.GetTokens(null, out cookieToken, out formToken);
            return cookieToken + ":" + formToken;                
        }
}

2.Get token and set header before send to server

var token = '@TokenHeaderValue()';    

       $http({
           method: "POST",
           url: './MainBackend/MessageDelete',
           data: dataSend,
           headers: {
               'RequestVerificationToken': token
           }
       }).success(function (data) {
           alert(data)
       });

3. Onserver Validation on HttpRequestBase on method you handle Post/get

        string cookieToken = "";
        string formToken = "";
        string[] tokens = Request.Headers["RequestVerificationToken"].Split(':');
            if (tokens.Length == 2)
            {
                cookieToken = tokens[0].Trim();
                formToken = tokens[1].Trim();
            }
        AntiForgery.Validate(cookieToken, formToken);

Slight improvement to 360Airwalk solution. This imbeds the Anti Forgery Token within the javascript function, so @Html.AntiForgeryToken() no longer needs to be included on every view.

$(document).ready(function () {
    var securityToken = $('@Html.AntiForgeryToken()').attr('value');
    $('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);
            }
        }
    });
});
共15条数据 1 尾页
Csdn user default icon
上传中...
上传图片
插入图片
抄袭、复制答案,以达到刷声望分或其他目的的行为,在CSDN问答是严格禁止的,一经发现立刻封号。是时候展现真正的技术了!
立即提问
相关内容推荐