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:
- Am I correct in thinking that Darin's solution assumes the existence of a form element?
- What's an elegant way to mash-up Darin's Web API filter with Johan's MVC 4 RC code?
Thanks in advance!
If it helps anyone, in .net core, the header's default value is actually just "RequestVerificationToken", without the "__". So if you change the header's key to that instead, it'll work.
You can also override the header name if you like:
services.AddAntiforgery(o => o.HeaderName = "__RequestVerificationToken")
Just wanted to add that this approach worked for me also (.ajax posting JSON to a Web API endpoint), although I simplified it a bit by inheriting from ActionFilterAttribute and overriding the OnActionExecuting method.
Extension method using Darin's answer, with a check for the presence of the header. The check means that the resulting error message is more indicative of what's wrong ("The required anti-forgery form field "__RequestVerificationToken" is not present.") versus "The given header was not found."
ApiController Usage:
An implementation using AuthorizeAttribute:
Then just decorate your controller or methods with [ApiValidateAntiForgeryToken]
And add to the razor file this to generate your token for javascript:
You could try reading from the headers:
Note:
GetCookies
is an extension method that exists in the classHttpRequestHeadersExtensions
which is part ofSystem.Net.Http.Formatting.dll
. It will most likely exist inC:\Program Files (x86)\Microsoft ASP.NET\ASP.NET MVC 4\Assemblies\System.Net.Http.Formatting.dll