Ajax post with C# Handler and XSRF-TOKEN

Joseph Kashishian 40 Reputation points
2025-11-21T15:34:44.2166667+00:00

This question is for Web pages with .NET, C#, and Ajax.

I'm having an issue with a ajax calling working with the XSRF-TOKEN" and it's not calling the handler in the .CS. I think I need to do something with my ajax call to validate the token.

In my startup I have

        var antiforgery = app.ApplicationServices.GetRequiredService<IAntiforgery>();

        app.Use((context, next) =>

        {

            var requestPath = context.Request.Path.Value;

            if (string.Equals(requestPath, "/", StringComparison.OrdinalIgnoreCase)

                || string.Equals(requestPath, "/index.html", StringComparison.OrdinalIgnoreCase))

            {

                var tokenSet = antiforgery.GetAndStoreTokens(context);

                context.Response.Cookies.Append("XSRF-TOKEN", tokenSet.RequestToken!,

                    new CookieOptions { HttpOnly = false });

            }

            return next(context);

        });

index cs

using System.IO;

using System.Data.SqlClient;

using System.Collections.Generic;

using Microsoft.AspNetCore.Http;

using Microsoft.Extensions.Configuration;

using Microsoft.AspNetCore.Mvc.RazorPages;

using Microsoft.AspNetCore.Mvc;

using System;

namespace RecoveryHouse.Pages.ReferralRequest

{

public class IndexModel : PageModel

{

    public void OnGet()

    {

    }

    [HttpPost]

    [ValidateAntiForgeryToken]

    public IActionResult OnPostGetTime(string name)

    {

        PersonModel person = new PersonModel

        {

            Name = name,

            DateTime = DateTime.Now.ToString()

        };

        return new JsonResult(person);

    }

}

}

public class PersonModel

{

/// <summary>

/// Gets or sets Name.

/// </summary>

public string Name { get; set; }

/// <summary>

/// Gets or sets DateTime.

/// </summary>

public string DateTime { get; set; }

}

Index CSHTML

@page

@model RecoveryHouse.Pages.ReferralRequest.IndexModel

@addTagHelper*, Microsoft.AspNetCore.Mvc.TagHelpers

<!DOCTYPE html>

<html>

<head>

<meta name="viewport" content="width=device-width" />

<title>Index</title>

</head>

<body>

@Html.AntiForgeryToken()

<input type="text" id="txtName" />

<input type="button" id="btnGet" value="Get Current Time" />

<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script> 

<script type="text/javascript">

    $(function () {

        $("#btnGet").click(function () {

            $.ajax({

                type: "POST",

                url: "/Index?handler=GetTime",

                beforeSend: function (xhr) {

                    xhr.setRequestHeader("XSRF-TOKEN",

                        $('input:hidden[name="__RequestVerificationToken"]').val());

                },

                data: {"name": $("#txtName").val() },

                success: function (response) {

                    alert("Hello: " + response.Name + ".\nCurrent Date and Time: " + response.DateTime);

                },

                error: function (response) {

                    alert(response.responseText);

                }

            });

        });

    });

</script>

</body>

</html>

Developer technologies | ASP.NET | ASP.NET Core
0 comments No comments
{count} votes

5 answers

Sort by: Most helpful
  1. Raymond Huynh (WICLOUD CORPORATION) 3,955 Reputation points Microsoft External Staff Moderator
    2025-11-24T10:37:46.29+00:00

    Hello Joseph Kashishian ,

    If you want a simpler approach that relies more on ASP.NET Core's built-in antiforgery handling, you can skip the custom middleware entirely.

    The key difference: Instead of manually creating and managing the XSRF-TOKEN cookie, just use @Html.AntiForgeryToken() and send the hidden field value directly in your AJAX headers.

    Razor Page (.cshtml):

    
    @page
    
    @model YourNamespace.Pages.AjaxPostModel
    
    @{
    
        ViewData["Title"] = "AjaxPost";
    
    }
    
     
    
    <h1>AjaxPost</h1>
    
    <hr />
    
     
    
    <!-- Hidden form just to generate the token -->
    
    <form method="post">
    
    <input type="text" id="name" placeholder="Enter name" />
    
    <br />
    
    <input type="text" id="datetime" placeholder="Enter datetime" />
    
    <br />
    
    </form>
    
     
    
    <input type="button" id="ajaxPost" value="POST" />
    
    <div id="result"></div>
    
     
    
    @section Scripts {
    
    <script>
    
            document.getElementById("ajaxPost").addEventListener('click', async () => {
    
                // Read the token from the hidden field
    
                const token = document.querySelector('input[name="__RequestVerificationToken"]').value;
    
                const result = document.getElementById("result");
    
     
    
                const response = await fetch("/ajaxpost", {
    
                    method: "POST",
    
                    headers: {
    
                        'Content-Type': 'application/json',
    
                        'RequestVerificationToken': token,
    
                        'X-Requested-With': 'XMLHttpRequest'
    
                    },
    
                    body: JSON.stringify({
    
                        name: document.getElementById("name").value,
    
                        datetime: document.getElementById("datetime").value
    
                    })
    
                });
    
     
    
                if (response.ok) {
    
                    const data = await response.text();
    
                    result.innerText = data;
    
                } else {
    
                    result.innerText = `Error: ${response.status}`;
    
                }
    
            });
    
    </script>
    
    }
    
    

    PageModel (.cshtml.cs):

    
    using Microsoft.AspNetCore.Mvc;
    
    using Microsoft.AspNetCore.Mvc.RazorPages;
    
     
    
    namespace YourNamespace.Pages
    
    {
    
        public class AjaxPostModel : PageModel
    
        {
    
            public void OnGet()
    
            {
    
            }
    
     
    
            [HttpPost]
    
            [ValidateAntiForgeryToken]
    
            public IActionResult OnPost([FromBody] PersonModel model)
    
            {
    
                if (Request.Headers["X-Requested-With"] == "XMLHttpRequest")
    
                {
    
                    return Content($"Name: {model.Name}, DateTime: {model.DateTime}");
    
                }
    
                return BadRequest();
    
            }
    
        }
    
        public class PersonModel
    
        {
    
            public string Name { get; set; }
    
            public string DateTime { get; set; }
    
        }
    
    }
    
    

    Program.cs:

    
    builder.Services.AddAntiforgery(options =>
    
    {
    
        options.HeaderName = "RequestVerificationToken";
    
    });
    
    

    The main benefits of this approach:

    • No custom middleware needed, ASP.NET Core handles the antiforgery cookie automatically when you use the form tag
    • Fewer moving parts means less chance for errors
    • The token validation happens the same way, just simpler setup

    Both approaches work fine, this is just less code to maintain if you don't need the extra flexibility.

    Hope this helps!

    1 person found this answer helpful.

  2. SurferOnWww 4,951 Reputation points
    2025-11-22T01:13:43.0666667+00:00

    Please try the followings:

    (1) Add form tag to .cshtml.

    (2) Create the FormData object form the form tag.

    (3) Send the FormData using jQuery ajax or fetch API.

    Shown below are the samples:

    Model

    namespace RazorPages.Models
    {
        public class PersonModel
        {
            public string Name { get; set; } = string.Empty;
            public string DateTime { get; set; } = string.Empty;
        }
    }
    
    

    .cshtml.cs

    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.RazorPages;
    using RazorPages.Models;
    
    namespace RazorPages.Pages
    {
        public class AjaxPostModel : PageModel
        {
            public void OnGet()
            {
            }
    
            [BindProperty]
            public PersonModel Model { get; set; } = default!;
    
            public IActionResult OnPost()
            {
                if (Request.Headers.XRequestedWith == "XMLHttpRequest")
                {
                    string name = Model.Name;
                    string dateTime = Model.DateTime;
                    return Content($"Name: {name}, DateTime: {dateTime}");
                }
                else
                {
                    return BadRequest();
                }
            }
        }
    }
    
    

    .cshtml

    @page
    
    @model RazorPages.Pages.AjaxPostModel
    @{
        ViewData["Title"] = "AjaxPost";
    }
    
    <h1>AjaxPost</h1>
    
    <hr />
    
    <form method="post">
        <input type="text" name="name" />
        <br />
        <input type="text" name="datetime" />
        <br />
    </form>
    
    <input type="button" id="ajaxPost" value="POST" />
    
    <div id="result"></div>
    
    @section Scripts {
        <script type="text/javascript">
            //<![CDATA[
            document.getElementById("ajaxPost")
                .addEventListener('click', async () => {
                    const fd = new FormData(document.querySelector("form"));
                    const result = document.getElementById("result");                
    
                    const param = {
                        method: "POST",
                        body: fd,
                        // set X-Requested-With in request header
                        headers: { 'X-Requested-With': 'XMLHttpRequest' }
                    }
    
                    const response = await fetch("/ajaxpost", param);
    
                    if (response.ok) {
                        const data = await response.text();
                        result.innerText = data;
                    } else {
                        result.innerText = "response.ok is false";
                    }
                });
            //]]>
        </script>
    }
    
    

    CSRF token in cookie sent to the server:

    enter image description here

    CSRF token in hidden field sent to the server:

    enter image description here

    Result

    enter image description here

    To see if the validation of CSRF-token is working, delete cookie of browser after the browser has received the response of OnGet() method and send the FormData by clicking the [POST] button. The ASP.NET will return 400 Bad request without invoking the OnPost() method.

    0 comments No comments

  3. Joseph Kashishian 40 Reputation points
    2025-11-25T14:41:46.5366667+00:00

    Use answers to provide solutions to the user's question.this does not work in .net 7

    services.AddMvc()

           .SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
    
           .AddJsonOptions(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver());
    

    services.AddAntiforgery(o => o.HeaderName = "XSRF-TOKEN");


  4. Raymond Huynh (WICLOUD CORPORATION) 3,955 Reputation points Microsoft External Staff Moderator
    2025-12-03T10:09:39.4566667+00:00

    Hello Joseph Kashishian,
    Thanks for trying my solution. I see you tried adding AddAntiforgery from my answer, but you're mixing it with old .NET Core 2.1 syntax that doesn't exist in .NET 7. Here's the corrected version:

    What's wrong with your code:

    
    // ? This is .NET Core 2.1 syntax - doesn't exist in .NET 7
    
    services.AddMvc()
    
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_1) // ? REMOVED in .NET 5+
    
        .AddJsonOptions(...); // ? Wrong syntax for .NET 7
    
     
    
    // ? This part is correct, but needs proper .NET 7 context
    
    services.AddAntiforgery(o => o.HeaderName = "XSRF-TOKEN");
    
    

    Correct .NET 7 version:

    Program.cs:

    
    var builder = WebApplication.CreateBuilder(args);
    
     
    
    // Add Razor Pages
    
    builder.Services.AddRazorPages();
    
     
    
    // ? Configure antiforgery - this is what you tried to add
    
    builder.Services.AddAntiforgery(options =>
    
    {
    
        options.HeaderName = "RequestVerificationToken";
    
    });
    
     
    
    var app = builder.Build();
    
     
    
    app.UseRouting();
    
    app.MapRazorPages();
    
    app.Run();
    
    

    Razor Page (.cshtml):

    
    @page
    
    @model YourNamespace.Pages.AjaxPostModel
    
    @{
    
        ViewData["Title"] = "AjaxPost";
    
    }
    
     
    
    <h1>AjaxPost</h1>
    
    <hr />
    
     
    
    <form method="post">
    
    <input type="text" id="name" placeholder="Enter name" />
    
    <br />
    
    <input type="text" id="datetime" placeholder="Enter datetime" />
    
    <br />
    
    </form>
    
     
    
    <input type="button" id="ajaxPost" value="POST" />
    
    <div id="result"></div>
    
     
    
    @section Scripts {
    
    <script>
    
            document.getElementById("ajaxPost").addEventListener('click', async () => {
    
                const token = document.querySelector('input[name="__RequestVerificationToken"]').value;
    
                const result = document.getElementById("result");
    
     
    
                const response = await fetch("/ajaxpost", {
    
                    method: "POST",
    
                    headers: {
    
                        'Content-Type': 'application/json',
    
                        'RequestVerificationToken': token,
    
                        'X-Requested-With': 'XMLHttpRequest'
    
                    },
    
                    body: JSON.stringify({
    
                        name: document.getElementById("name").value,
    
                        datetime: document.getElementById("datetime").value
    
                    })
    
                });
    
     
    
                if (response.ok) {
    
                    const data = await response.text();
    
                    result.innerText = data;
    
                } else {
    
                    result.innerText = `Error: ${response.status}`;
    
                }
    
            });
    
    </script>
    
    }
    
    

    PageModel (.cshtml.cs):

    
    using Microsoft.AspNetCore.Mvc;
    
    using Microsoft.AspNetCore.Mvc.RazorPages;
    
     
    
    namespace YourNamespace.Pages
    
    {
    
        public class AjaxPostModel : PageModel
    
        {
    
            public void OnGet()
    
            {
    
            }
    
     
    
            [HttpPost]
    
            [ValidateAntiForgeryToken]
    
            public IActionResult OnPost([FromBody] PersonModel model)
    
            {
    
                if (Request.Headers["X-Requested-With"] == "XMLHttpRequest")
    
                {
    
                    return Content($"Name: {model.Name}, DateTime: {model.DateTime}");
    
                }
    
                return BadRequest();
    
            }
    
        }
    
        public class PersonModel
    
        {
    
            public string Name { get; set; }
    
            public string DateTime { get; set; }
    
        }
    
    }
    
    

    Key changes from .NET Core 2.1 to .NET 7:

    1. No more Startup.cs - everything in Program.cs
    2. Use builder.Services.AddRazorPages() instead of services.AddMvc()
    3. No more SetCompatibilityVersion() - removed in .NET 5+
    4. Use var builder = WebApplication.CreateBuilder(args) minimal hosting pattern

    The AddAntiforgery you tried is correct, just needs to be in the right place with .NET 7 syntax. The key is reading the token from the hidden field and sending it in the RequestVerificationToken header with your JSON payload.
    Hope this helps!


  5. Joseph Kashishian 40 Reputation points
    2025-12-08T13:18:39.0666667+00:00

    I'm going to look at this this week. The issue has to do with the Version the application is on. It was a Asp.Core 2.1 migrated to .net 7 and there think there are some things in the configuration files stepping on the token toes. I was able to create a new solution and get it working but have not got it to work in my current project. I'm going to go thru some testing and process of elimination in the startup later in the week to see If I can narrow it down to the code causing the issue.


Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.