Sending an anti-forgery token with ASP.NET Core MVC AJAX requests

Pier-Luc Bonneville
Pier-Luc Bonneville
Being technical is important at the leadership level in a world where IT is eating the world.
Aug 30, 2020 4 min read
thumbnail for this post
Photo by analogicus

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 and
  • application/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.

AJAX post 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.

HTTP headers with the request verification token

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

References

  1. jQuery.ajax() | jQuery API Documentation
  2. Fetch API
  3. Cross Site Request Forgery (CSRF)
  4. Prevent Cross-Site Request Forgery (XSRF/CSRF) attacks in ASP.NET Core
  5. Binding source parameter inference