Sending an anti-forgery token with ASP.NET Core MVC AJAX requests
Introduction
Updated to .NET 7.0
To prevent Cross-Site Request Forgery (CSRF) attacks, OWASP recommends to always protect POST/PUT requests using an anti-forgery token.
Although trivial when using an HTML <form>
element for submitting information, things get a bit trickier when attempting to submit the same information in an asynchronous HTTP (Ajax) request.
Here is the view-model we’ll be using for this example.
using System.ComponentModel.DataAnnotations;
public class PersonViewModel
{
public int Id { get; set; }
[Required]
public string Firstname { get; set; }
[Required]
public string Lastname { get; set; }
}
In our controller, we’ll add the [ValidateAntiForgeryToken]
attribute on the [HttpPost]
and [HttpPut]
actions.
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult PostAjaxData(PersonViewModel personViewModel)
{
// Todo: Save person to database
return Json(new { Id = personViewModel.Id });
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult PostAjaxJson([FromBody] PersonViewModel personViewModel)
{
// Todo: Save person to database
return Json(new { Id = personViewModel.Id });
}
}
Notice that we have two [HttpPost]
actions as we are demonstrating how to submit data from the view to the controller using both:
application/x-www-form-urlencoded; charset=UTF-8
andapplication/json; charset=utf-8
formats.
Using jQuery, we can craft the Ajax request like so.
<button onclick="submitData()">Submit data</button>
@section Scripts {
<script>
function submitData() {
const person = { firstname: "John", lastname: "Doe" };
$.ajax({
type: "POST",
url: "@Url.Action("PostAjaxData")",
data: person,
success: function (msg) {
console.log(msg);
}
});
}
</script>
}
The problem
Submitting the request without the token, you end up with a 400 HTTP status code.
The solution
Using jQuery
In order to validate the anti-forgery token using AJAX requests, you’ll need to manually add the token to the request headers.
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf
@functions
{
public string GetAntiXsrfRequestToken()
{
return Xsrf.GetAndStoreTokens(Context).RequestToken;
}
}
<h1>Anti-forgery token with ASP.NET Core MVC AJAX requests</h1>
<button onclick="submitData()">Submit data</button>
<button onclick="submitJsonData()">Submit JSON data</button>
@section Scripts {
<script>
function submitData() {
const person = { firstname: "John", lastname: "Doe" };
$.ajax({
type: "POST",
url: "@Url.Action("PostAjaxData")",
headers: { "RequestVerificationToken": "@GetAntiXsrfRequestToken()" },
data: person,
success: function (data) {
console.log(data.id);
},
error: function (req, status, error) {
alert(error);
}
});
}
function submitJsonData() {
const person = { firstname: "John", lastname: "Doe" };
$.ajax({
type: "POST",
url: "@Url.Action("PostAjaxJson")",
headers: { "RequestVerificationToken": "@GetAntiXsrfRequestToken()" },
dataType: "json",
contentType: "application/json; charset=utf-8",
data: JSON.stringify(person),
success: function (data) {
console.log(data.id)
},
error: function (req, status, error) {
alert(error);
}
});
}
</script>
}
Using fetch API
Here is the same code using the fetch
API instead of jQuery.ajax()
.
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf
@functions
{
public string GetAntiXsrfRequestToken()
{
return Xsrf.GetAndStoreTokens(Context).RequestToken;
}
}
<h1>Anti-forgery token with ASP.NET Core MVC AJAX requests</h1>
<button onclick="submitJsonData()">Submit JSON data</button>
@section Scripts {
<script>
function submitJsonData() {
const person = { firstname: "John", lastname: "Doe" };
fetch("@Url.Action("PostAjaxJson")", {
method: "POST",
headers: {
"Content-Type": "application/json; charset=utf-8",
"RequestVerificationToken": "@GetAntiXsrfRequestToken()"
},
body: JSON.stringify(person)
})
.then((response) => response.json())
.then((data) => console.log(data.id))
.catch((err) => console.log(err));
}
</script>
}
Using axios
And now, for axios
.
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf
@functions
{
public string GetAntiXsrfRequestToken()
{
return Xsrf.GetAndStoreTokens(Context).RequestToken;
}
}
<h1>Anti-forgery token with ASP.NET Core MVC AJAX requests</h1>
<button onclick="submitJsonData()">Submit JSON data</button>
@section Scripts {
<script src="~/lib/axios/axios.min.js"></script>
<script>
function submitJsonData() {
const person = { firstname: "John", lastname: "Doe" };
axios({
method: "POST",
url: "@Url.Action("PostAjaxJson")",
data: JSON.stringify(person),
headers: {
"RequestVerificationToken": "@GetAntiXsrfRequestToken()",
"Content-Type": "application/json;charset=UTF-8"
}
})
.then((response) => {
console.log(response.data.id);
}, (error) => {
console.log(error);
});
}
</script>
}
The request header now contains a request verification token that can be used to prevent (CSRF) attacks using the [ValidateAntiForgeryToken]
in the controller.
Conclusion
In this post, we covered how to send data with an anti-forgery token header using an Ajax request by means of the jQuery.ajax()
method, the fetch
API, and using the axios
client.
In addition, we saw how to send JSON serialized data using an Ajax request to an action.
The code covered in this blog post is available here:
blog-examples/tree/master/sending-an-anti-forgery-token-with-aspnet-core-mvc-ajax-requests