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. 

1. 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[...]">

2. 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)
{
    ...
}

3. 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!