I had to be a bit shadowy to check anti-fake tokens when posting JSON, but it worked.
//If it not a GET, and the data they're sending is a string (since we already had a separate solution in place for form-encoded data), then add the verification token to the URL, if it not already there. $.ajaxSetup({ beforeSend: function (xhr, options) { if (options.type && options.type.toLowerCase() !== 'get' && typeof (options.data) === 'string' && options.url.indexOf("?__RequestVerificationToken=") < 0 && options.url.indexOf("&__RequestVerificationToken=") < 0) { if (options.url.indexOf('?') < 0) { options.url += '?'; } else { options.url += '&'; } options.url += "__RequestVerificationToken=" + encodeURIComponent($('input[name=__RequestVerificationToken]').val()); } } });
But, as several people have already mentioned, validation only checks the form - not JSON, not the query string. So, we redefined the attribute behavior. Re-implementing the entire validation would be horrible (and probably unsafe), so I just overridden the Form property, if the token was passed to the QueryString, it has a built-in validation, THINK, which was on the form.
This is a bit complicated because the form is read-only, but doable.
if (IsAuth(HttpContext.Current) && !IsGet(HttpContext.Current)) { //if the token is in the params but not the form, we sneak in our own HttpContext/HttpRequest if (HttpContext.Current.Request.Params != null && HttpContext.Current.Request.Form != null && HttpContext.Current.Request.Params["__RequestVerificationToken"] != null && HttpContext.Current.Request.Form["__RequestVerificationToken"] == null) { AntiForgery.Validate(new ValidationHttpContextWrapper(HttpContext.Current), null); } else { AntiForgery.Validate(new HttpContextWrapper(HttpContext.Current), null); } } //don't validate un-authenticated requests; anyone could do it, anyway private static bool IsAuth(HttpContext context) { return context.User != null && context.User.Identity != null && !string.IsNullOrEmpty(context.User.Identity.Name); } //only validate posts because that what CSRF is for private static bool IsGet(HttpContext context) { return context.Request.HttpMethod.ToUpper() == "GET"; }
...
internal class ValidationHttpContextWrapper : HttpContextBase { private HttpContext _context; private ValidationHttpRequestWrapper _request; public ValidationHttpContextWrapper(HttpContext context) : base() { _context = context; _request = new ValidationHttpRequestWrapper(context.Request); } public override HttpRequestBase Request { get { return _request; } } public override IPrincipal User { get { return _context.User; } set { _context.User = value; } } } internal class ValidationHttpRequestWrapper : HttpRequestBase { private HttpRequest _request; private System.Collections.Specialized.NameValueCollection _form; public ValidationHttpRequestWrapper(HttpRequest request) : base() { _request = request; _form = new System.Collections.Specialized.NameValueCollection(request.Form); _form.Add("__RequestVerificationToken", request.Params["__RequestVerificationToken"]); } public override System.Collections.Specialized.NameValueCollection Form { get { return _form; } } public override string ApplicationPath { get { return _request.ApplicationPath; } } public override HttpCookieCollection Cookies { get { return _request.Cookies; } } }
There are other things that differ from our solution (in particular, we use the HttpModule, so we do not need to add an attribute to each POST), which I forgot in favor of brevity. I can add it if necessary.
DrShaffopolis Jul 15 '13 at 23:09 2013-07-15 23:09
source share