include antiforgerytoken in ajax post ASP.NET MVC
Asked Answered
R

16

206

I am having trouble with the AntiForgeryToken with ajax. I'm using ASP.NET MVC 3. I tried the solution in jQuery Ajax calls and the Html.AntiForgeryToken(). Using that solution, the token is now being passed:

var data = { ... } // with token, key is '__RequestVerificationToken'

$.ajax({
        type: "POST",
        data: data,
        datatype: "json",
        traditional: true,
        contentType: "application/json; charset=utf-8",
        url: myURL,
        success: function (response) {
            ...
        },
        error: function (response) {
            ...
        }
    });

When I remove the [ValidateAntiForgeryToken] attribute just to see if the data (with the token) is being passed as parameters to the controller, I can see that they are being passed. But for some reason, the A required anti-forgery token was not supplied or was invalid. message still pops up when I put the attribute back.

Any ideas?

EDIT

The antiforgerytoken is being generated inside a form, but I'm not using a submit action to submit it. Instead, I'm just getting the token's value using jquery and then trying to ajax post that.

Here is the form that contains the token, and is located at the top master page:

<form id="__AjaxAntiForgeryForm" action="#" method="post">
    @Html.AntiForgeryToken()
</form>
Ruttger answered 23/1, 2013 at 6:18 Comment(0)
C
336

You have incorrectly specified the contentType to application/json.

Here's an example of how this might work.

Controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Index(string someValue)
    {
        return Json(new { someValue = someValue });
    }
}

View:

@using (Html.BeginForm(null, null, FormMethod.Post, new { id = "__AjaxAntiForgeryForm" }))
{
    @Html.AntiForgeryToken()
}

<div id="myDiv" data-url="@Url.Action("Index", "Home")">
    Click me to send an AJAX request to a controller action
    decorated with the [ValidateAntiForgeryToken] attribute
</div>

<script type="text/javascript">
    $('#myDiv').submit(function () {
        var form = $('#__AjaxAntiForgeryForm');
        var token = $('input[name="__RequestVerificationToken"]', form).val();
        $.ajax({
            url: $(this).data('url'),
            type: 'POST',
            data: { 
                __RequestVerificationToken: token, 
                someValue: 'some value' 
            },
            success: function (result) {
                alert(result.someValue);
            }
        });
        return false;
    });
</script>
Cohe answered 23/1, 2013 at 6:32 Comment(9)
Hi, thanks for the quick reply. Sorry I didn't mention it in the question; I'm not using the submit action at the moment. (The token is in a form, but i'm not using a submit button to submit it). Is it possible just to change the content type to something else?Odontoblast
The fact that you are not using a submit action doesn't change my answer much. All you need to do is subscribe to some other event (a button click, an anchor click or whatever and simply read the hidden field value). As far as sending the AJAX request is concerned you could use the example provided in my answer. You should not use contentType to application/json because the server is expecting the __RequestVerificationToken parameter to be part of the POST request payload using application/x-www-form-urlencoded.Cohe
how this code $(this).data('url'), can understand what would be the url of my controller and action. please explain. thanksCanticle
The original question was about contentType: 'application/json'. For regular ajax posts including the __RequestVerificationToken in the form post will obviously work because it is like a regular form post. When you want to post json however (hence the content type) this does not seem to work. So this is a case of incorrectly accepting the above as an answer.Norven
Do I need to use "ModelState.IsValid" ? How can I tell that this is working?Troopship
Hi @DarinDimitrov, thanks for the solution, but I am having an issue. While testing when I am not providing "__RequestVerificationToken: token" in ajax post, I am getting 500 internal server error from jquery file instead I want to send user to an error page.Esmeralda
@DarinDimitrov i have student input fields in html from and my controller method look like this public ActionResult Index(StudentModel model) how can i make ajax request ?Minter
model: $('#frm-password-reset').serialize() this not workMinter
Is it better to use the default contentType rather than application/json, What are the consequences if we changing a solution already using application/json to the default application/x-www-form-urlencoded ? I mean we can use this approach to apply anti XSRF but is there any hurt in performance from sending data objects instead of stringify data ?Cavatina
B
70

it is so simple! when you use @Html.AntiForgeryToken() in your html code it means that server has signed this page and each request that is sent to server from this particular page has a sign that is prevented to send a fake request by hackers. so for this page to be authenticated by the server you should go through two steps:

1.send a parameter named __RequestVerificationToken and to gets its value use codes below:

<script type="text/javascript">
    function gettoken() {
        var token = '@Html.AntiForgeryToken()';
        token = $(token).val();
        return token;
   }
</script>

for example take an ajax call

$.ajax({
    type: "POST",
    url: "/Account/Login",
    data: {
        __RequestVerificationToken: gettoken(),
        uname: uname,
        pass: pass
    },
    dataType: 'json',
    contentType: 'application/x-www-form-urlencoded; charset=utf-8',
    success: successFu,
});

and step 2 just decorate your action method by [ValidateAntiForgeryToken]

Bonne answered 22/2, 2016 at 14:31 Comment(2)
Thanks,works great for json post ... i was missing contentType :(Unmannerly
Thanks. Nice idea with using $(htmlWithInputString).val() to get token. I did it with data attribute (to avoid inline scripts in html). Something like this <div class="js-html-anti-forgery-token" data-anti-forgery-token-html-input="@(Html.AntiForgeryToken().ToString())"> in HTML and $($(".js-html-anti-forgery-token").data("antiForgeryTokenHtmlInput")).val() in JS.Kreutzer
R
67

Another (less javascriptish) approach, that I did, goes something like this:

First, an Html helper

public static MvcHtmlString AntiForgeryTokenForAjaxPost(this HtmlHelper helper)
{
    var antiForgeryInputTag = helper.AntiForgeryToken().ToString();
    // Above gets the following: <input name="__RequestVerificationToken" type="hidden" value="PnQE7R0MIBBAzC7SqtVvwrJpGbRvPgzWHo5dSyoSaZoabRjf9pCyzjujYBU_qKDJmwIOiPRDwBV1TNVdXFVgzAvN9_l2yt9-nf4Owif0qIDz7WRAmydVPIm6_pmJAI--wvvFQO7g0VvoFArFtAR2v6Ch1wmXCZ89v0-lNOGZLZc1" />
    var removedStart = antiForgeryInputTag.Replace(@"<input name=""__RequestVerificationToken"" type=""hidden"" value=""", "");
    var tokenValue = removedStart.Replace(@""" />", "");
    if (antiForgeryInputTag == removedStart || removedStart == tokenValue)
        throw new InvalidOperationException("Oops! The Html.AntiForgeryToken() method seems to return something I did not expect.");
    return new MvcHtmlString(string.Format(@"{0}:""{1}""", "__RequestVerificationToken", tokenValue));
}

that will return a string

__RequestVerificationToken:"P5g2D8vRyE3aBn7qQKfVVVAsQc853s-naENvpUAPZLipuw0pa_ffBf9cINzFgIRPwsf7Ykjt46ttJy5ox5r3mzpqvmgNYdnKc1125jphQV0NnM5nGFtcXXqoY3RpusTH_WcHPzH4S4l1PmB8Uu7ubZBftqFdxCLC5n-xT0fHcAY1"

so we can use it like this

$(function () {
    $("#submit-list").click(function () {
        $.ajax({
            url: '@Url.Action("SortDataSourceLibraries")',
            data: { items: $(".sortable").sortable('toArray'), @Html.AntiForgeryTokenForAjaxPost() },
            type: 'post',
            traditional: true
        });
    });
});

And it seems to work!

Rento answered 17/4, 2013 at 10:23 Comment(6)
+1, nice. I just split the @Html.AntiForgeryTokenForAjaxPost in two in order to get the token name in one hand and its value in the other. Otherwise the syntax highlight is all messed up. It ends up like this (removed the single-quotes from the returned result too, so that it behaves like any MVC helper, for instance @Url): '@Html.AntiForgeryTokenName' : '@Html.AntiForgeryTokenValue'Araarab
nit nice. With this You have ajax call n cshtm file.... you should not mox js with razor that much in my opinion.Superload
I have downvoted this question because I believe that a simpler approach is to use AntiForgery static class. Getting HTML and replacing it instead of directly getting the token value is bad practice. ASP.NET is fully open source: github.com/ASP-NET-MVC/aspnetwebstack/blob/… (but now it could be worth to write another answer with a custom extension method that gets only the token)Berm
A cleaner way of getting just the token value would be to use XElement. XElement.Parse(antiForgeryInputTag).Attribute("value").ValueAlfonzoalford
@Alfonzoalford can you post a snippet around this pleaseSampan
@transformer var antiForgeryInputTag = helper.AntiForgeryToken().ToString(); return XElement.Parse(antiForgeryInputTag).Attribute("value").ValueAlfonzoalford
V
16

In Asp.Net Core you can request the token directly, as documented:

@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf    
@functions{
    public string GetAntiXsrfRequestToken()
    {
        return Xsrf.GetAndStoreTokens(Context).RequestToken;
    }
}

And use it in javascript:

function DoSomething(id) {
    $.post("/something/todo/"+id,
               { "__RequestVerificationToken": '@GetAntiXsrfRequestToken()' });
}

You can add the recommended global filter, as documented:

services.AddMvc(options =>
{
    options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
})

Update

The above solution works in scripts that are part of the .cshtml. If this is not the case then you can't use this directly. My solution was to use a hidden field to store the value first.

My workaround, still using GetAntiXsrfRequestToken:

When there is no form:

<input type="hidden" id="RequestVerificationToken" value="@GetAntiXsrfRequestToken()">

The name attribute can be omitted since I use the id attribute.

Each form includes this token. So instead of adding yet another copy of the same token in a hidden field, you can also search for an existing field by name. Please note: there can be multiple forms inside a document, so name is in that case not unique. Unlike an id attribute that should be unique.

In the script, find by id:

function DoSomething(id) {
    $.post("/something/todo/"+id,
       { "__RequestVerificationToken": $('#RequestVerificationToken').val() });
}

An alternative, without having to reference the token, is to submit the form with script.

Sample form:

<form id="my_form" action="/something/todo/create" method="post">
</form>

The token is automatically added to the form as a hidden field:

<form id="my_form" action="/something/todo/create" method="post">
<input name="__RequestVerificationToken" type="hidden" value="Cf..." /></form>

And submit in the script:

function DoSomething() {
    $('#my_form').submit();
}

Or using a post method:

function DoSomething() {
    var form = $('#my_form');

    $.post("/something/todo/create", form.serialize());
}
Vernettaverneuil answered 4/10, 2018 at 15:44 Comment(1)
I think this solution only works if your javascript is also in your cshtml file.Amoebaean
C
8

In Asp.Net MVC when you use @Html.AntiForgeryToken() Razor creates a hidden input field with name __RequestVerificationToken to store tokens. If you want to write an AJAX implementation you have to fetch this token yourself and pass it as a parameter to the server so it can be validated.

Step 1: Get the token

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

Step 2: Pass the token in the AJAX call

function registerStudent() {

var student = {     
    "FirstName": $('#fName').val(),
    "LastName": $('#lName').val(),
    "Email": $('#email').val(),
    "Phone": $('#phone').val(),
};

$.ajax({
    url: '/Student/RegisterStudent',
    type: 'POST',
    data: { 
     __RequestVerificationToken:token,
     student: student,
        },
    dataType: 'JSON',
    contentType:'application/x-www-form-urlencoded; charset=utf-8',
    success: function (response) {
        if (response.result == "Success") {
            alert('Student Registered Succesfully!')

        }
    },
    error: function (x,h,r) {
        alert('Something went wrong')
      }
})
};

Note: The content type should be 'application/x-www-form-urlencoded; charset=utf-8'

I have uploaded the project on Github; you can download and try it.

https://github.com/lambda2016/AjaxValidateAntiForgeryToken

Cloudland answered 20/7, 2016 at 15:20 Comment(1)
How can i use form serialize here student: $('#frm-student').serialize(),Minter
S
7


        function DeletePersonel(id) {

                var data = new FormData();
                data.append("__RequestVerificationToken", "@HtmlHelper.GetAntiForgeryToken()");

                $.ajax({
                    type: 'POST',
                    url: '/Personel/Delete/' + id,
                    data: data,
                    cache: false,
                    processData: false,
                    contentType: false,
                    success: function (result) {

                    }
                });

        }
    

        public static class HtmlHelper
        {
            public static string GetAntiForgeryToken()
            {
                System.Text.RegularExpressions.Match value = System.Text.RegularExpressions.Regex.Match(System.Web.Helpers.AntiForgery.GetHtml().ToString(), "(?:value=\")(.*)(?:\")");
                if (value.Success)
                {
                    return value.Groups[1].Value;
                }
                return "";
            }
        }
Stancil answered 1/9, 2016 at 16:39 Comment(0)
S
4

In Account controller:

    // POST: /Account/SendVerificationCodeSMS
    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public JsonResult SendVerificationCodeSMS(string PhoneNumber)
    {
        return Json(PhoneNumber);
    }

In View:

$.ajax(
{
    url: "/Account/SendVerificationCodeSMS",
    method: "POST",
    contentType: 'application/x-www-form-urlencoded; charset=utf-8',
    dataType: "json",
    data: {
        PhoneNumber: $('[name="PhoneNumber"]').val(),
        __RequestVerificationToken: $('[name="__RequestVerificationToken"]').val()
    },
    success: function (data, textStatus, jqXHR) {
        if (textStatus == "success") {
            alert(data);
            // Do something on page
        }
        else {
            // Do something on page
        }
    },
    error: function (jqXHR, textStatus, errorThrown) {
        console.log(textStatus);
        console.log(jqXHR.status);
        console.log(jqXHR.statusText);
        console.log(jqXHR.responseText);
    }
});

It is important to set contentType to 'application/x-www-form-urlencoded; charset=utf-8' or just omit contentTypefrom the object ...

Shotputter answered 9/11, 2018 at 18:12 Comment(1)
not really practical, means you have to code up every form , and if forms have a lot of elements it could be a pain :(Hausa
H
3

I know this is an old question. But I will add my answer anyway, might help someone like me.

If you dont want to process the result from the controller's post action, like calling the LoggOff method of Accounts controller, you could do as the following version of @DarinDimitrov 's answer:

@using (Html.BeginForm("LoggOff", "Accounts", FormMethod.Post, new { id = "__AjaxAntiForgeryForm" }))
{
    @Html.AntiForgeryToken()
}

<!-- this could be a button -->
<a href="#" id="ajaxSubmit">Submit</a>

<script type="text/javascript">
    $('#ajaxSubmit').click(function () {

        $('#__AjaxAntiForgeryForm').submit();

        return false;
    });
</script>
Histogram answered 27/6, 2015 at 21:22 Comment(0)
K
3

For me the solution was to send the token as a header instead of as a data in the ajax call:

    $.ajax({
       type: "POST",
       url: destinationUrl,
       data: someData,
       headers:{
           "RequestVerificationToken": token
       },
       dataType: "json",
       success: function (response) {
           successCallback(response);
       },
       error: function (xhr, status, error) {
          // handle failure
       }
   });
Kalimantan answered 31/10, 2022 at 13:55 Comment(0)
E
1

The token won't work if it was supplied by a different controller. E.g. it won't work if the view was returned by the Accounts controller, but you POST to the Clients controller.

Evetta answered 13/8, 2019 at 9:19 Comment(0)
I
0

I tried a lot of workarrounds and non of them worked for me. The exception was "The required anti-forgery form field "__RequestVerificationToken" .

What helped me out was to switch form .ajax to .post:

$.post(
    url,
    $(formId).serialize(),
    function (data) {
        $(formId).html(data);
    });
Ironhanded answered 19/7, 2016 at 15:6 Comment(0)
P
0

Feel free to use the function below:

function AjaxPostWithAntiForgeryToken(destinationUrl, successCallback) {
var token = $('input[name="__RequestVerificationToken"]').val();
var headers = {};
headers["__RequestVerificationToken"] = token;
$.ajax({
    type: "POST",
    url: destinationUrl,
    data: { __RequestVerificationToken: token }, // Your other data will go here
    dataType: "json",
    success: function (response) {
        successCallback(response);
    },
    error: function (xhr, status, error) {
       // handle failure
    }
});

}

Planck answered 19/9, 2018 at 16:36 Comment(0)
A
0

Create a method that will responsible to add token

var addAntiForgeryToken = function (data) {
    data.__RequestVerificationToken = $("[name='__RequestVerificationToken']").val();
    return data;
};

Now use this method while passing data/parameters to Action like below

 var Query = $("#Query").val();
        $.ajax({
            url: '@Url.Action("GetData", "DataCheck")',
            type: "POST",
            data: addAntiForgeryToken({ Query: Query }),
            dataType: 'JSON',
            success: function (data) {
            if (data.message == "Success") {
            $('#itemtable').html(data.List);
            return false;
            }
            },
            error: function (xhr) {
            $.notify({
            message: 'Error',
            status: 'danger',
            pos: 'bottom-right'
            });
            }
            });

Here my Action have a single parameter of string type

    [HttpPost]
    [ValidateAntiForgeryToken]
    public JsonResult GetData( string Query)
    {
Asphaltite answered 11/8, 2021 at 6:37 Comment(0)
E
0
 @using (Ajax.BeginForm("SendInvitation", "Profile",
        new AjaxOptions { HttpMethod = "POST", OnSuccess = "SendInvitationFn" },
        new { @class = "form-horizontal", id = "invitation-form" }))
    {
        @Html.AntiForgeryToken()
        <span class="red" id="invitation-result">@Html.ValidationSummary()</span>

        <div class="modal-body">
            <div class="row-fluid marg-b-15">
                <label class="block">                        
                </label>
                <input type="text" id="EmailTo" name="EmailTo" placeholder="[email protected]" value="" />
            </div>
        </div>

        <div class="modal-footer right">
            <div class="row-fluid">
                <button type="submit" class="btn btn-changepass-new">send</button>
            </div>
        </div>
    }
Ettieettinger answered 17/11, 2021 at 8:55 Comment(0)
D
0

I've struggle with this and come up with a solution that's both declarative and doesn't require lots of client or server changes. This works for ASPNET MVC .NET 4.8 but can easily be done in .NET Core too.

In brief:

  • Use ASPNET MVC AntiForgery.Validate() and AntiForgery.GetTokens().
  • Keep the token in a page as a hidden field.
  • Get the token from page and "push" it along all JSON calls transparently using jQuery.ajaxSend().
  • Handle the received token using ValidateAntiForgeryJSONToken (a custom FilterAttribute).

Benefits:

  • Just decorate controller actions with the [ValidateAntiForgeryJSONToken].
  • Add token once.
  • Push token along JSON centrally.

Here's the code:

ValidateAntiForgeryJSONToken attibute

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
    public class ValidateAntiForgeryJSONToken : FilterAttribute, IAuthorizationFilter
    {
        public ValidateAntiForgeryJSONToken()
        {
        }

        public virtual void OnAuthorization(AuthorizationContext filterContext)
        {
            if (filterContext == null)
                throw new ArgumentNullException(nameof(filterContext));

            var request = filterContext.RequestContext.HttpContext.Request;
            if (request == null)
                return;

            var stream = request.InputStream;
            var encoding = request.ContentEncoding;
            var reader = new StreamReader(stream, encoding);
            var json = reader.ReadToEnd();
            request.InputStream.Position = 0;
            var token = "";
            try
            {
                JObject o = JObject.Parse(json);
                var jToken = o.GetValue("ajaxAFT", StringComparison.InvariantCultureIgnoreCase);
                token = jToken.ToString();
            }
            catch (Exception)
            {
            }

            ValidateAntiForgeryToken(token);
        }

        public static void ValidateAntiForgeryToken(string token)
        {
            string cookieToken = "";
            string formToken = "";

            if (!String.IsNullOrWhiteSpace(token))
            {
                string[] tokens = token.Split(':');
                if (tokens.Length == 2)
                {
                    cookieToken = tokens[0].Trim();
                    formToken = tokens[1].Trim();
                }
            }

            try
            {
                AntiForgery.Validate(cookieToken, formToken);
            }
            catch (Exception)
            {
                throw new BaseException();
            }
        }
    }

Controller

 
        [HttpPost]
        [ValidateAntiForgeryJSONToken]
        public ActionResult TestAJAXCall(string id)
        {
        }

Ajax interception

'use strict';

$(document)
    .ajaxSend(function (event, jqXHR, ajaxSettings) {
        if (ajaxSettings !== undefined) {
            try {
                let payload = JSON.parse(ajaxSettings.data);
                let token = payload.ajaxAFT;
                if (token === undefined) {
                    token = $("#_ajaxAFT").val();
                    payload.ajaxAFT = token;
                    ajaxSettings.data = JSON.stringify(payload);
                }
            } catch (e) {

            }
        }
    });

Razor token injection

 @Html.AjaxAntiForgeryToken()

Razor Token helper

public static HtmlString AjaxAntiForgeryToken(this HtmlHelper helper)
        {
            string cookieToken, formToken;
            AntiForgery.GetTokens(null, out cookieToken, out formToken);
            var myToken = cookieToken + ":" + formToken;

            var model = new FormHiddenInputFormModel()
            {
                Id = "_ajaxAFT",
                Value = myToken

            };
            return helper.Partial("Forms/_FormHiddenInputText", model);
        }
Disclimax answered 30/3, 2023 at 17:7 Comment(0)
M
0

All the previous answers may work but check that:

ProcessData: true

If you set it to False your validation will fail.

You can also omit ProcessData because the default is True

Marchland answered 1/3 at 10:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.