jQuery Ajax calls and the Html.AntiForgeryToken()

前端 未结 20 2356
鱼传尺愫
鱼传尺愫 2020-11-22 16:34

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

相关标签:
20条回答
  • 2020-11-22 17:27

    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);
                }
            }
        });
    });
    
    0 讨论(0)
  • 2020-11-22 17:27
    function DeletePersonel(id) {
    
        var data = new FormData();
        data.append("__RequestVerificationToken", "@HtmlHelper.GetAntiForgeryToken()");
    
        $.ajax({
            type: 'POST',
            url: '/Personel/Delete/' + id,
            data: data,
            cache: false,
            processData: false,
            contentType: false,
            success: function (result) {
            }
        });
    }
    
    public static class HtmlHelper {
        public static string GetAntiForgeryToken() {
            System.Text.RegularExpressions.Match value = 
                    System.Text.RegularExpressions.Regex.Match(System.Web.Helpers.AntiForgery.GetHtml().ToString(), 
                            "(?:value=\")(.*)(?:\")");
            if (value.Success) {
                return value.Groups[1].Value;
            }
            return "";
        }
    }
    
    0 讨论(0)
  • 2020-11-22 17:28

    I like the solution provided by 360Airwalk, but it may be improved a bit.

    The first problem is that if you make $.post() with empty data, jQuery doesn't add a Content-Type header, and in this case ASP.NET MVC fails to receive and check the token. So you have to ensure the header is always there.

    Another improvement is support of all HTTP verbs with content: POST, PUT, DELETE etc. Though you may use only POSTs in your application, it's better to have a generic solution and verify that all data you receive with any verb has an anti-forgery token.

    $(document).ready(function () {
        var securityToken = $('[name=__RequestVerificationToken]').val();
        $(document).ajaxSend(function (event, request, opt) {
            if (opt.hasContent && securityToken) {   // handle all verbs with content
                var tokenParam = "__RequestVerificationToken=" + encodeURIComponent(securityToken);
                opt.data = opt.data ? [opt.data, tokenParam].join("&") : tokenParam;
                // ensure Content-Type header is present!
                if (opt.contentType !== false || event.contentType) {
                    request.setRequestHeader( "Content-Type", opt.contentType);
                }
            }
        });
    });
    
    0 讨论(0)
  • 2020-11-22 17:29

    I feel like an advanced necromancer here, but this is still an issue 4 years later in MVC5.

    To handle ajax requests properly the anti-forgery token needs to be passed to the server on ajax calls. Integrating it into your post data and models is messy and unnecessary. Adding the token as a custom header is clean and reusable - and you can configure it so you don't have to remember to do it every time.

    There is an exception - Unobtrusive ajax does not need special treatment for ajax calls. The token is passed as usual in the regular hidden input field. Exactly the same as a regular POST.

    _Layout.cshtml

    In _layout.cshtml I have this JavaScript block. It doesn't write the token into the DOM, rather it uses jQuery to extract it from the hidden input literal that the MVC Helper generates. The Magic string that is the header name is defined as a constant in the attribute class.

    <script type="text/javascript">
        $(document).ready(function () {
            var isAbsoluteURI = new RegExp('^(?:[a-z]+:)?//', 'i');
            //http://stackoverflow.com/questions/10687099/how-to-test-if-a-url-string-is-absolute-or-relative
    
            $.ajaxSetup({
                beforeSend: function (xhr) {
                    if (!isAbsoluteURI.test(this.url)) {
                        //only add header to relative URLs
                        xhr.setRequestHeader(
                           '@.ValidateAntiForgeryTokenOnAllPosts.HTTP_HEADER_NAME', 
                           $('@Html.AntiForgeryToken()').val()
                        );
                    }
                }
            });
        });
    </script>
    

    Note the use of single quotes in the beforeSend function - the input element that is rendered uses double quotes that would break the JavaScript literal.

    Client JavaScript

    When this executes the beforeSend function above is called and the AntiForgeryToken is automatically added to the request headers.

    $.ajax({
      type: "POST",
      url: "CSRFProtectedMethod",
      dataType: "json",
      contentType: "application/json; charset=utf-8",
      success: function (data) {
        //victory
      }
    });
    

    Server Library

    A custom attribute is required to process the non standard token. This builds on @viggity's solution, but handles unobtrusive ajax correctly. This code can be tucked away in your common library

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
    public class ValidateAntiForgeryTokenOnAllPosts : AuthorizeAttribute
    {
        public const string HTTP_HEADER_NAME = "x-RequestVerificationToken";
    
        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            var request = filterContext.HttpContext.Request;
    
            //  Only validate POSTs
            if (request.HttpMethod == WebRequestMethods.Http.Post)
            {
    
                var headerTokenValue = request.Headers[HTTP_HEADER_NAME];
    
                // Ajax POSTs using jquery have a header set that defines the token.
                // However using unobtrusive ajax the token is still submitted normally in the form.
                // if the header is present then use it, else fall back to processing the form like normal
                if (headerTokenValue != null)
                {
                    var antiForgeryCookie = request.Cookies[AntiForgeryConfig.CookieName];
    
                    var cookieValue = antiForgeryCookie != null
                        ? antiForgeryCookie.Value
                        : null;
    
                    AntiForgery.Validate(cookieValue, headerTokenValue);
                }
                else
                {
                    new ValidateAntiForgeryTokenAttribute()
                        .OnAuthorization(filterContext);
                }
            }
        }
    }
    

    Server / Controller

    Now you just apply the attribute to your Action. Even better you can apply the attribute to your controller and all requests will be validated.

    [HttpPost]
    [ValidateAntiForgeryTokenOnAllPosts]
    public virtual ActionResult CSRFProtectedMethod()
    {
      return Json(true, JsonRequestBehavior.DenyGet);
    }
    
    0 讨论(0)
  • 2020-11-22 17:32

    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.

    0 讨论(0)
  • 2020-11-22 17:33

    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) {
            // ....
        }
    });
    
    0 讨论(0)
提交回复
热议问题