In ASP.NET Core, if we use jQuery Ajax to post data to the server, and we want the ValidateAntiForgeryToken attribute to work. We have to do some tricks. The official document didn't document how to do it via jQuery. Let me show you how to do it.
Please do read the official document first: https://docs.microsoft.com/en-us/aspnet/core/security/anti-request-forgery?view=aspnetcore-2.1
In my practice, there's difference between official document and my scenarios.
Token Generation
First, we still need a hidden input to store the CSRF token, instead of this code in official document:
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf
@functions{
public string GetAntiXsrfRequestToken()
{
return Xsrf.GetAndStoreTokens(Context).RequestToken;
}
}
<input type="hidden" id="RequestVerificationToken"
name="RequestVerificationToken" value="@GetAntiXsrfRequestToken()">
I use an easier way:
@Html.AntiForgeryToken()
This has some benifits. First, of course it can save few lines of code. But most importantly, because I have defined customized names for the CSRF tokens like this:
services.AddAntiforgery(options =>
{
options.Cookie.Name = "X-CSRF-TOKEN-MOONGLADE";
options.FormFieldName = "CSRF-TOKEN-MOONGLADE-FORM";
});
The backend Controller will use these names to validate the request. And I don't want to forget to change every place where there is a manual written input tag. So basically, @Html.AntiForgeryToken() will generate HTML based on my definition in Startup.cs like this. And if some day I changed the name in antiforgery options, I don't need to worry about forget to change it everywhere in my cshtml pages.
<input name="CSRF-TOKEN-MOONGLADE-FORM" type="hidden" value="CfDJ8BBX[...]">
Controller and Action
This has no different than a normal action method. just put [ValidateAntiForgeryToken] attribute is enough.
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
[Route("delete")]
public IActionResult Delete(Guid pingbackId)
{
...
}
jQuery Code
This is where the real magic happens, in official document, the JavaScript code only add headers to the request:
document.addEventListener('DOMContentLoaded', function() {
document.getElementById("antiforgery").onclick = function () {
xhttp.open('POST', '@Url.Action("Antiforgery", "Home")', true);
xhttp.setRequestHeader("RequestVerificationToken",
document.getElementById('RequestVerificationToken').value);
xhttp.send();
}
});
We can do this in jQuery also:
$.ajax({
...
headers: {
"CSRF-TOKEN-MOONGLADE-FORM": $('input[name="CSRF-TOKEN-MOONGLADE-FORM"]').val()
},
...
});
However, it won't work! I still don't know why, but I found a fix to this, which is including the CSRF values into the posted form data.
I use $.extend function to include the "CSRF-TOKEN-MOONGLADE-FORM" values:
var pData = $.extend({ pingbackId: pingbackId },
{ "CSRF-TOKEN-MOONGLADE-FORM": $('input[name="CSRF-TOKEN-MOONGLADE-FORM"]').val()});
Which now makes the full Ajax code like this:
$(function () {
$(".btn-delete").click(function () {
deletePingback($(this).data("pingbackid"));
});
});
function deletePingback(pingbackId) {
$("#span-processing-" + pingbackId).show();
var pData = $.extend({ pingbackId: pingbackId }, { "CSRF-TOKEN-MOONGLADE-FORM": $('input[name="CSRF-TOKEN-MOONGLADE-FORM"]').val()});
$.ajax({
type: "POST",
url: "pingback/delete",
headers: {
"CSRF-TOKEN-MOONGLADE-FORM": $('input[name="CSRF-TOKEN-MOONGLADE-FORM"]').val()
},
data: pData,
success: function (data) {
$("#pingback-box-" + data).slideUp();
},
dataType: "json"
});
}
Now the Ajax request can be validated against the backend MVC action!
If you application has a lot of places where CSRF tokens need to be posted via Ajax, you can extract a common method to make life easier:
function makeCSRFExtendedData(data) {
var extData = $.extend(data, { "CSRF-TOKEN-MOONGLADE-FORM": $('input[name="CSRF-TOKEN-MOONGLADE-FORM"]').val() });
return extData;
}
function ajaxPostWithCSRFToken(url, pData, funcSuccess) {
$.ajax({
type: "POST",
url: url,
headers: {
"CSRF-TOKEN-MOONGLADE-FORM": $('input[name="CSRF-TOKEN-MOONGLADE-FORM"]').val()
},
data: makeCSRFExtendedData(pData),
success: function (data) {
funcSuccess(data);
},
dataType: "json"
});
}
And use it like this:
ajaxPostWithCSRFToken("delete", { commentId: cid }, function (data) {
$("#panel-comment-" + data).slideUp();
});
Ken
Question ... when I add the "token in data" to the post direct it does fail as you mentioned. But when I add using your function, the post fails completely and get this .. {"Error":{"Code":"UnsupportedApiVersion","Message":"The HTTP resource that matches the request URI 'http://localhost:4533/Admin/User/Index' with API version '1.0' does not support HTTP method 'GET'.","InnerError":null}}. It thinks it is a get ... thoughts
Raymond
I have been using ajax with anti-forgery. The one question I have have, since the token can be found on the page is it possible for someone to use Postman and call the end point? Or put a button on a page (via the console) and call it?
David
This a great article. I have a problem where this will fail for me if my azure access token ha* **pired. Do you have any examples showing how to manage the token refresh with jquery?
HMZ
It won't work because you have to set the anti-forgery token header name in the service configuration then you won't need the form input submitted with the ajax call
Filippo
As suggested by HMZ, to avoid inserting the token in the data sent, it is necessary to insert
services.AddAntiforgery (o => o.HeaderName = "XSRF-TOKEN");
in ConfigureServices.