ajaxasp.net-core-mvcantiforgerytoken

Correct way to send AntiForgeryToken through AJAX request


I've been trying to send the AntiForgeryToken through AJAX request in an ASP.NET Core MVC (area based) Razor view; however, I am definitely missing something to send in the POST request. I read through several documentations and StackO. answers, but all of them are missing something common that I found on this MS documentation: Preventing Cross-Site Request Forgery (CSRF) Attacks in ASP.NET MVC Application. I looked at this StackO. question and the replies, and the common difference is that the MS documentation sends both the cookie and form (hidden element) token but I get an error message saying AntiForgery.GetTokens(null, out cookieToken, out formToken) does not exist in the current context.

I also found this and thought could be really helpful but don't know how to implement into the view.

This SO question is the same as my question, only difference being I want to still use the normal (and non-customized) [ValidateAntiForgeryToken] attribute.

I want to know and learn the right and most secure way to send both the cookie and form antiforgery tokens through ajax post requests while using the [ValidateAntiForgeryToken] attribute. Additionally, if there are any flaws or security or usage issues in the code, please do correct me! Any help would be greatly appreciated! Thanks!

Here's my code in the MVC view:

@{
    ViewData["Title"] = "Kiosk";
}
@model ...
@using (Html.BeginForm("Index", "Kiosk"))
{
    @Html.AntiForgeryToken()
}
@using Microsoft.AspNetCore.Antiforgery

<h1>Check-in Kiosk</h1>
<title>@ViewData["Title"]</title>

<h2>Follow the instructions below.</h2>

<p id="instructions" style="color: green;">
    Slide your ID card into the card-holder and wait for further instructions.
</p>

<div id="row">
    <div class="col-md-4">
        <form method="post" onsubmit="getData()">
            <div asp-validation-summary="All" class="text-danger pb-2" id="errors"></div>
            
            <div class="form-floating pb-2">
                <input asp-for="ScannedCode" class="form-control" id="scancode"/>
                <label asp-for="ScannedCode"></label>
                <span asp-validation-for="ScannedCode" class="text-danger"></span>
            </div>

            <div class="form-floating pb-2">
                <input asp-for="Number" class="form-control" id="number"/>
                <label asp-for="Number"></label>
                <span asp-validation-for="Number" class="text-danger"></span>
            </div>

            <div class="form-group pt-4">
                <input type="submit" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<script>

    function getFormToken() {
        let token = $('input[name="`__RequestVerificationToken`"]').val();
        return token;
    }

    @functions {
        public string TokenHeaderValue()
        {
            string cookieToken, formToken;
            AntiForgery.GetTokens(null, out cookieToken, out formToken);
            return cookieToken + ":" + formToken;
        }
    }

    function getData() {
        let scannedCode = document.getElementById("scancode").innerText;
        let number = document.getElementById("number").innerText;

        sendData(scannedCode, number);
    }

    function sendData(ScannedCode, Number) {
        let realdata = { 
            ScannedCode: ScannedCode;
            Number: Number;
        };
        
        $.ajax(
            {
                type: "POST",
                url: "@Url.Action("Index", "Kiosk")",
                data: realdata,
                dataType: 'json',
                headers: 'RequestVerificationToken': '@TokenHeaderValue()',
                success: function (response) {
                    if (response.success == true) {
                        document.getElementById("id").hidden;
                        document.getElementById("instructions").innerText = response.message;


                        setTimeout(function () {
                            location.reload();
                        }, response.seconds);
                    }
                    else {
                        document.getElementById("instructions").innerText = response.message;


                        setTimeout(function () {
                            location.reload();
                        }, response.seconds);
                    }
                }
            }
        )
    }
</script>

Here's the method simply decorated with the [ValidateAntiForgeryToken] attribute:

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index([Bind("ScannedCode,Number")] CodeModel input) {
//Implementation not shown
}

Solution

  • You can use below code

    let token = $('input[name="__RequestVerificationToken"]').val();
    

    to get token, here is my test result and test code.

    Test Result

    enter image description here

    enter image description here

    KioskController.cs

    using System.Diagnostics;
    using Microsoft.AspNetCore.Mvc;
    using MVCWebApplication.Models;
    
    namespace MVCWebApplication.Controllers;
    
    public class KioskController  : Controller
    {
        private readonly ILogger<KioskController> _logger;
    
        public KioskController (ILogger<KioskController > logger)
        {
            _logger = logger;
        }
    
        [HttpGet]
        public IActionResult Index()
        {
            return View();
        }
    
        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Index([Bind("ScannedCode,Number")] TestModel input)
        {
            return Json(new { success = true, message = "Data received successfully", seconds = 3 });
        }
    }
    

    TestModel.cs

    namespace MVCWebApplication.Models
    {
       public class TestModel
        {
            public string? ScannedCode { get; set; }
            public string? Number { get; set; }
        }
    }
    

    /Kiosk/Index.cshtml

    @{
        ViewData["Title"] = "Kiosk";
    }
    @model MVCWebApplication.Models.TestModel
    
    @using (Html.BeginForm("Index", "Kiosk", FormMethod.Post, new { id = "kioskForm" }))
    {
        @Html.AntiForgeryToken()
        <div class="form-floating pb-2">
            <input asp-for="ScannedCode" class="form-control" id="scancode"/>
            <label asp-for="ScannedCode"></label>
            <span asp-validation-for="ScannedCode" class="text-danger"></span>
        </div>
    
        <div class="form-floating pb-2">
            <input asp-for="Number" class="form-control" id="number"/>
            <label asp-for="Number"></label>
            <span asp-validation-for="Number" class="text-danger"></span>
        </div>
    
        <div class="form-group pt-4">
            <button type="button" class="btn btn-primary" onclick="getData()">Submit</button>
        </div>
    }
    
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script>
        function getData() {
            let scannedCode = $('#scancode').val();
            let number = $('#number').val();
    
            sendData(scannedCode, number);
        }
    
        function sendData(scannedCode, number) {
            let realdata = { 
                ScannedCode: scannedCode,
                Number: number
            };
    
            let token = $('input[name="__RequestVerificationToken"]').val();
    
            $.ajax({
                type: "POST",
                url: "@Url.Action("Index", "Kiosk")",
                data: realdata,
                headers: {
                    "RequestVerificationToken": token
                },
                success: function (response) {
                    if (response.success) {
                        $('#instructions').text(response.message);
    
                        setTimeout(function () {
                            location.reload();
                        }, response.seconds * 1000);
                    } else {
                        $('#instructions').text(response.message);
                    }
                },
                error: function (xhr, status, error) {
                    console.error(xhr.responseText);
                }
            });
        }
    </script>
    
    <h1>Check-in Kiosk</h1>
    <p id="instructions" style="color: green;">
        Slide your ID card into the card-holder and wait for further instructions.
    </p>