The first step in diagnosing issues with AJAX requests is to actually examine the request itself. There are many tools available for this, but the most accessible tools are the developer tools provided by the major browsers. They are accessible from within Chrome, IE, Edge and Firefox by pressing F12 while viewing your web page, or Ctrl+Shift+I in Opera. This latter key combination also works for FireFox. In all cases, a panel should open in the browser that looks something like this:
The highlighted elements are the Network tab, which is currently open and shows details of the browser's network traffic, and the All option which is selected by default. This option results in all network traffic being recorded and reported on. The option that sits to the right - XHR - filters out all requests except those initiated by the XmlHttpRequest object or the browser's Fetch API. In other words, if you select the XHR option, you will only see details of AJAX requests.
Once you have this panel open, you are in a much better position to fault find. And the first potential point of failure to rule out is whether the AJAX request is actually being made at all. If nothing appears in the grid, the request is not being made. This could be for any number of reasons. If there is an error in the JavaScript code, an inidcator will appear (arrowed, showing Chrome then Edge) and details will appear in the Console tab.
If there are no errors reported, but the request is still not made in
response to the trigger action, check to see if the event handler that initiates
the request is wired to the trigger element correctly. If the request is made,
details will appear, hopefully accompanied by a nice 200
HTTP status code to
signify that all has gone well. However, as alluded to in the title of this
article, the status code that causes the most confusion is 400
:
According to the HTTP standards:
The 400 (Bad Request) status code indicates that the server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing).
So there is an error in the way that the post
request has
been constructed, but what is it? The most common cause for a 400
Bad Request
status code in Razor Pages is a missing Request Verification token.
Request Verification Tokens
Request verification is a process that ensures that post
requests received by your application originate from the application, and not
from a third party. Request verification is an important tool in the armoury
against
cross site request forgery (CSRF) attacks, and is enabled by default in Razor Pages.
A hidden form field named
__RequestVerificationToken
is rendered within every form by the
form tag helper. It contains an encrypted token. The same value is included in a cookie which is sent with the form request. The presence of both of these
tokens and their values are validated when ASP.NET Core processes a POST request. If verification fails, the framework returns a
400
status code.
During normal form submission, the hidden field containing the CSRF token is automatically included in
the payload. The most common causes for failed AJAX posts resulting in a 400
status code are:
- The CSRF token was generated but the was not included in the posted payload
- The CSRF token was included in the post, but in a way that prevented its discovery on the server.
- No CSRF token was generated because the form tag helper was not used
No hidden field was generated
You need to generate a CSRF token value. While the form tag helper is the most
common means of generating the token value as a hidden field, it is not the only
means. In fact, it may not even make sense to generate form tags or hidden
fields for your scenario - espcially if you are not posting form field values.
If it does make sense to include a form in your page, then do so, but ensure
that the method
attribute is set to post
. CSRF
tokens are not generated for forms that use the get
method.
If you don't need a form, you can inject the IAntiforgery
interface into your
page, and use it to generate a token value:
@page @model MyApp.Pages.IndexModel @inject IAntiforgery antiforgery @{ var token = antiforgery.GetAndStoreTokens(HttpContext).RequestToken; }
You will need to either use the fully qualified namespace (Microsoft.AspNetCore.Antiforgery
)
to reference IAntiforgery
, or add a using statement to the
_ViewImports file. The token can then be used in the AJAJX post.
Including the CSRF token in the post
You can construct the data for your AJAX post in any number of ways.
If you use jQuery, its serialize()
method will take care of ensuring that all the specified form values are
properly encoded for submission. When using the Fetch API, you can construct a
FormData
object from the relevant form element, which will also
ensure that all fields, including hidden fields are included in the payload. You
may be doing this, but still your posts generate 400
codes. If so,
check to ensure that you are not stringifying your serialised form values to
JSON (using e.g. JSON.stringify
) unnecessarily as this will prevent
the token being discovered by the server.
If you construct the values yourself, it is easy to overlook the verification token.
The verification token can be included manually in the post in two ways, as part of
the request body, or in a header.
By default, the header must be named
RequestVerificationToken
and the form field's name must be __RequestVerificationToken
,
although you can customise them through configuration via the
AntiforgeryOptions
in ConfigureServices
e.g.
services.AddAntiforgery(o => o.HeaderName = "CSRF-TOKEN");
Acording to OWASP, inserting the CSRF token in the HTTP request header via JavaScript is considered more secure than adding the token in the hidden field form parameter.
If you are using jQuery, you can use the $.ajax
method instead of
$.post
, because it enables you access to more options, including setting headers:
$.ajax({ type: "POST", url: "/", data: { foo: "bar" }, headers: { "RequestVerificationToken": $('input[name="__RequestVerificationToken"]').val() }, success: function (response) { ...
Here's how you can accomplish the same thing using Fetch:
var token = document.querySelector('input[name="__RequestVerificationToken"]').getAttribute("value"); fetch("", { method: "post", headers: { "RequestVerificationToken": token }, body: { foo: "bar" } }).then(() => { ... });
If the CSRF token is included as a form field and a header value, the header
value takes precedence. If the header value fails verification, the system will
not fall back to the form field. It will simpy return a BadRequestResult
,
generating a 400
response code.
Summary
This article provides some basic guidance on troubleshooting failing AJAX
post requests, and then focuses on the most common culprit in ASP.NET Core Razor
Pages. It explains what the CSRF token is, how it is generated by Razor Pages,
and how you can generate one using the IAntiforgery
API. It also
explains how to manually include the token in your AJAX post so that it can be
discovered.