'Use the ValidateAntiForgeryToken attribute with JSON POST data

Some of my controller methods have the [ValidateAntiForgeryToken] attribute for the usual reasons. It's only app-internal actions, no cross-site API or similar. Now I replaced a request made from JavaScript from jQuery (which seems to send data as form fields) with a real JSON post using fetch() directly. The __RequestVerificationToken field was among the sent data so it must have ended up in a place where ASP.NET Core MVC was looking for it.

Now it's in the JSON body, there are no form fields anymore. And the request fails with code 400, probably due to the missing (not found) token.

I've searched for solutions but this has so far only been covered for the older non-Core ASP.NET from 10 years ago. Is it still possible today with current tools to send the token in the JSON body or as HTTP header (I'm fine with either one) and have it validated without much boilerplate code? I can add a special attribute class for that if needed. I already looked at the framework class but it doesn't do anything, this must be handled elsewhere.



Solution 1:[1]

Below is a work demo, you can refer to it. Read this to know more.

1.Customize AntiforgeryOptions in Program.cs:

builder.Services.AddAntiforgery(options =>
{
    // Set Cookie properties using CookieBuilder properties†.        
    options.HeaderName = "X-CSRF-TOKEN-HEADERNAME";        
});

2.Require antiforgery validation

public IActionResult Index()
{
    // ...

    return View();
}

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Privacy()
{
    // ...

    return View();
}

Index.cshtml:

@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Antiforgery

@{
    ViewData["Title"] = "JavaScript";

    var requestToken = Antiforgery.GetAndStoreTokens(Context).RequestToken;
}

<input id="RequestVerificationToken" type="hidden" value="@requestToken" />

<button id="button" class="btn btn-primary">Submit with Token</button>
<div id="result" class="mt-2"></div>

@section Scripts {
<script>
    document.addEventListener("DOMContentLoaded", () => {
        const resultElement = document.getElementById("result");

        document.getElementById("button").addEventListener("click", async () => {

            const response = await fetch("@Url.Action("Privacy")", {
                method: "POST",
                headers: {
                    RequestVerificationToken:
                        document.getElementById("RequestVerificationToken").value
                }
            });

            if (response.ok) {
                resultElement.innerText = await response.text();
            } else {
                resultElement.innerText = `Request Failed: ${response.status}`
            }
        });
    });
</script>
}

result:

enter image description here

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Qing Guo