36

My page has...

@page "{candidateId:int}"

... and

@Html.AntiForgeryToken()

Model has...

public void OnGet(int candidateId)
{

}

public void OnPost(int candidateId)
{

}

GET works fine. Here is my AJAX request..

$.ajax({
    type: "POST",
    url: "/Skills/" + candidateId,
    beforeSend: function (xhr) {

        xhr.setRequestHeader("XSRF-TOKEN",
            $('input:hidden[name="__RequestVerificationToken"]').val());
    },
    data: {

        name: 'hi mum'
    },

    success: function (response) {
    },
    failure: function (response) {

        alert(response);
    }
});

Browser receives useless error message... 400 Bad Request.

What am I missing?

Ian Warburton
  • 15,170
  • 23
  • 107
  • 189

1 Answers1

61

You are getting a 400 (Bad Request) response because the framework expects the RequestVerificationToken as part of the posted request.The framework uses this to prevent possible CSRF attacks. If your request does not have this information, the framework will return the 400 bad request. Your current code is not sending it.

Change the code to this

headers:
{
    "RequestVerificationToken": $('input:hidden[name="__RequestVerificationToken"]').val()
},

This will add a new item with key RequestVerificationToken to the request header and the framework should not throw a 400 response when the call is made. (assuming your view code generated the hidden input for the __RequestVerificationToken hidden input)

You can make the code more robust by injecting the IAntiforgery implementation to the view/page and using the GetAndStoreTokens method.

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

and call this GetAntiXsrfRequestToken function to get the value in your javascript

headers:
{
    "RequestVerificationToken": '@GetAntiXsrfRequestToken()'
},

You also probably want to use the PageModel's CandidateId property to create the url. Something like this

url: "/Skills/@Model.CandidateId",

Also, you do need to call @Html.AntiForgeryToken() method explicitly to generate the token input. Having a form with post method with no action attribute value will generate the hidden input for you.

<form method="post">
   <!-- your inputs-->
</form>
Shyju
  • 214,206
  • 104
  • 411
  • 497
  • 3
    "You are getting a 400 (Bad Request) response because the framework expects the RequestVerificationToken as part of the posted request." Where in the docs can I find this particular information? Please post a link. – PussInBoots Dec 13 '18 at 15:37
  • 3
    Had same problem and @Html.AntiForgeryToken() was missing in my form – Carl Verret Apr 26 '19 at 19:31
  • 2
    What if you need to accept a POST from an external site? A site where we cannot include the antiforgerytoken. – mxmissile May 03 '19 at 16:49
  • 16
    @mxmissile You can ignore validation of the anti forgery token by decorating your PageModel with `[IgnoreAntiforgeryToken(Order = 1001)]`. See more here: https://www.learnrazorpages.com/security/request-verification#opting-out – ajbeaven Dec 21 '19 at 11:32
  • you saved my say savior. – Khizar Iqbal Jun 16 '21 at 06:29
  • I also missing @Html.AntiForgeryToken() in model form in razor pages, – Roshan Nov 21 '21 at 15:15
  • I found a nice side effect is when using your method, I no longer need to use `@Html.AntiForgeryToken()` in the razor page (and no forms are declared). I assume that is expected? – lonix Jun 30 '22 at 10:36