I'm making JSON-based AJAX requests and, with MVC controllers have been very grateful to Phil Haack for his Preventing CSRF with AJAX and, Johan Driessen's Updated Anti-XSRF for MVC 4 RC. But, as I transition API-centric controllers to Web API, I'm hitting issues where the functionality between the two approaches is markedly different and I'm unable to transition the CSRF code.
ScottS raised a similar question recently which was answered by Darin Dimitrov. Darin's solution involves implementing an authorization filter which calls AntiForgery.Validate. Unfortunately, this code does not work for me (see next paragraph) and - honestly - is too advanced for me.
As I understand it, Phil's solution overcomes the problem with MVC AntiForgery when making JSON requests in the absence of a form element; the form element is assumed/expected by the AntiForgery.Validate method. I believe that this may be why I'm having problems with Darin's solution too. I receive an HttpAntiForgeryException "The required anti-forgery form field '__RequestVerificationToken' is not present". I am certain that the token is being POSTed (albeit in the header per Phil Haack's solution). Here's a snapshot of the client's call:
$token = $('input[name=""__RequestVerificationToken""]').val(); $.ajax({ url:/api/states", type: "POST", dataType: "json", contentType: "application/json: charset=utf-8", headers: { __RequestVerificationToken: $token } }).done(function (json) { ... });
I tried a hack by mashing together Johan's solution with Darin's and was able to get things working but am introducing HttpContext.Current, unsure whether this is appropriate/secure and why I can't use the provided HttpActionContext.
Here's my inelegant mash-up.. the change is the 2 lines in the try block:
public Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation) { try { var cookie = HttpContext.Current.Request.Cookies[AntiForgeryConfig.CookieName]; AntiForgery.Validate(cookie != null ? cookie.Value : null, HttpContext.Current.Request.Headers["__RequestVerificationToken"]); } catch { actionContext.Response = new HttpResponseMessage { StatusCode = HttpStatusCode.Forbidden, RequestMessage = actionContext.ControllerContext.Request }; return FromResult(actionContext.Response); } return continuation(); }
My questions are:
Thanks in advance!
To help prevent CSRF attacks, ASP.NET MVC uses anti-forgery tokens, also called request verification tokens. It verifies the tokens before accepting the request into ASP.NET MVC controller action which prevents the Cross Site Request Forgery.
Adding an AntiForgeryToken generates a Cryptographically valid hash at the server end which is split and a part is added as a hidden field, whereas the rest goes into a cookie. When data is posted, the Cookie and the Hidden Field are both sent back and if they are missing or they don't match, the POST is rejected.
Anti-forgery stands for “Act of copying or imitating things like a signature on a check, an official document to deceive the authority source for financial gains”. Now, in the case of web applications, it is termed as CSRF.
You could try reading from the headers:
var headers = actionContext.Request.Headers; var cookie = headers .GetCookies() .Select(c => c[AntiForgeryConfig.CookieName]) .FirstOrDefault(); var rvt = headers.GetValues("__RequestVerificationToken").FirstOrDefault(); AntiForgery.Validate(cookie != null ? cookie.Value : null, rvt);
Note: GetCookies
is an extension method that exists in the class HttpRequestHeadersExtensions
which is part of System.Net.Http.Formatting.dll
. It will most likely exist in C:\Program Files (x86)\Microsoft ASP.NET\ASP.NET MVC 4\Assemblies\System.Net.Http.Formatting.dll
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With