Table of Contents
If you've stumbled upon this article it most likely means that you're looking for a way to validate your ASP.NET Core Antiforgery Token when issuing an Ajax POST using jQuery or other JavaScript frameworks without getting the following HTTP Status Code error:
405 Method Not Allowed
If this error is preventing you from performing your Ajax POST requests to your controllers or Razor Pages, don't worry: you found the right place to fix your issue for good.
However, before releasing the fix, let's spend a couple minutes to recap how the Antiforgery token works in the context of ASP.NET Core Data Protection and why is important to learn how to properly deal with it instead of turning it off spamming the [IgnoreAntiforgeryToken] attribute in all of our Controllers that responds to AJAX or REST-based calls.
What is Antiforgery and why is it so important?
The purpose of anti-forgery tokens is to prevent cross-site request forgery (CSRF) attacks. The technique consists into submitting multiple different values to the server on any given HTTP POST request, both of which must exist to make the server accept the request: one of those values is submitted as a cookie and another one in the request's HTTP headers OR in a Form input data. If those values (tokens) don't exist or don't match, the request is denied with a 405 Method Not Allowed Status Code.
Now, while the cookie token is handled automatically by the browser, the form input data token has to be generated within the form: in most frameworks, this is often a task that the developers must implement on they own, as well as the server-side token verification task; luckily enough, the ASP.NET Core framework automatically handles such task thanks to the Antiforgery built-in Middleware.
How the ASP.NET Core AntiForgery works
As you probably already knows, ASP.NET Core implements antiforgery through a dedicated middleware tat gets added to the Dependency injection container when one of the following APIs is called in the ConfigureServices() method of the project's Startup.cs file:
- AddMvc
- MapRazorPages
- MapControllerRoute
- MapBlazorHub
Once added, the Antiforgery token is automatically appended to each and every HTML <form> element in all Razor Pages and Views in the form of a <input type="hidden"> HTML field similar to the following:
1 |
<input name="__RequestVerificationToken" type="hidden" value="__ANTIFORGERY_UNIQUE_ID__"> |
More precisely, the automatic generation of antiforgery tokens in <form> HTML elements takes place when the following conditions are met:
- the <form> tag contains the method="post" attribute;
- the action attribute is empty (action="") or not present;
When this element is generated and appended to the HTML Form, it gets POSTed to the back-end together with the ViewModel (or raw) form data: the back-end will then automatically validate it and accept or deny the request (with the 405 Method Not Allowed HTTP Error) depending on the validation result. The best part of the whole story is that we don't need to do anything, before all of these tasks are automatically handled by the framework in a seamless way.
This means that we can create this form in a Razor View:
1 2 3 |
<form asp-controller="Index" asp-action="Register" method="post"> <button type="submit">Submit</button> </form> |
Which will be rendered in the following way:
1 2 3 4 |
<form asp-controller="Index" asp-action="Register" method="post"> <button type="submit">Go</button> <input name="__RequestVerificationToken" type="hidden" value="__ANTIFORGERY_UNIQUE_ID__"> </form> |
And then validated by the Controller that will automatically before accepting the request and executing the corresponding Action Method's code:
1 2 3 4 5 6 7 |
public async Task<IActionResult> Index() { if (ModelState.IsValid) { /* do stuff */ } } |
As we can see, the developer doesn't need to do anything... not even the [ValidateAntiForgeryToken] and/or [AutoValidateAntiforgeryToken] attribute filters just like it was back in the day with ASP.NET Framework 4.x: the whole process is now enabled and active by default; in case we want to opt-out from the validation process and accept any requests, we can do that by decorating our Controllers (and/or Action Methods) with the [IgnoreAntiforgeryToken] attribute filter. However, doing that will leave that method exposed to CSRF-based attacks, thus weakening our web application.
Why we would ever want to do that?
If you managed to follow the story so far, you've probably already figured
The issue
If you managed to follow the story so far, you've probably already figured out where the problem actually lies: as we explained early on, the Antiforgery process is made up of two distinct actors that take place in two different moments within the HTTP request lifecycle:
- the form <input type="hidden"> tag, originated on the client-side and POSTed together with the form data and cookies;
- the verification process of such tokens, handled on the server-side by the Controller where the request is routed to;
Now, when the client-side form is generated within Razor Pages or Views, we've seen how everything will reasonably work in a seamless way because the input tag will be generated automatically; however, that's not the case when we issue AJAX Post requests using vanilla JavaScript or a JS framework such as jQuery.
To put it in other words, if we do something like this in our Razor Page of View:
1 2 3 4 5 6 |
$.ajax({ type: "POST", url: "https://www.my.url/Controller/Action", data: { my: "data" }, success: function() { /* do stuff */ } }); |
The antiforgery token won't be automatically generated, as ASP.NET Core doesn't know that syntax and won't be able to append anything to it.
What happens when that POST request hits the Action Method without the Antiforgery token? We already know that: the 405 Method Not Allowed HTTP Error.
The solution
In order to fix such issue, we have 2 options:
- add the [IgnoreAntiforgeryToken] attribute filter to our Controller's method;
- find a decent way to append the Antiforgery token to the Ajax request;
It goes without saying that the former option is highly discouraged, for a number of security considerations which we've already talked about early on: let's see how we can implement the latter.
#1. Looking around
The first thing to do is to go see how the Antiforgery system is configured in our web application.
Such info is usually present in the ConfigureServices() method of our app's Startup.cs file, where we should be able to find something like this:
1 |
services.AddAntiforgery(); |
If the service is configured in the following way, it means that the default values will be used: when this kind of scenario occurs, the <input type="hidden" /> that gets automatically appended to each Razor Page or View form has a name equals to __RequestVerificationToken.
However, most Visual Studio and Visual Studio Code templates uses a slightly different approach that includes some minimal level of personalization in the Cookie, Header and Form field names:
1 2 3 4 5 |
services.AddAntiforgery(options => { options.Cookie.Name = "X-CSRF-TOKEN-OurAppName"; options.HeaderName = "X-CSRF-TOKEN-OurAppName"; options.FormFieldName = "X-CSRF-TOKEN-OurAppName"; }); |
If our web application features something like this, it goes without saying that the <input type="hidden" /> will have a name attribute equals to X-CSFR-TOKEN-OurAppName.
Now that we know the input name, we know the first half of our answer: we definitely need to add a new key to our Ajax POST data object equals with the same name we've just discovered. However, we're still missing the most important part: a valid Antiforgery token value.
#2. Inject the IAntiforgery interface
The only way to retrieve a valid Antiforgery token is to have the Antiforgery system to generate one. In order to do that we need to find a way to access to that service from our Razor Pages or Views.
Luckily enough, we can inject it using the Dependency Injection pattern by adding the following code to the top of our Razor Page/View:
1 2 |
@using Microsoft.AspNetCore.Antiforgery @inject IAntiforgery antiforgery |
This will allow us to use the Antiforgery interface from within our Page/View in the following way:
1 2 3 |
@{ var tokenSet = antiforgery.GetAndStoreTokens(Context); } |
The tokenSet variable will be an instance of the AntiforgeryTokenSet class, which hosts an antiforgery token pair (cookie and request token) for a request. Once retrieved such object, we can do this:
1 2 3 4 5 6 7 8 9 |
$.ajax({ type: "POST", url: "https://www.my.url/Controller/Action", data: { my: "data", '@tokenSet.HeaderName' : '@tokenSet.RequestToken' }, success: function() { /* do stuff */ } }); |
As we can see, we didn't even use the form's input field name that we discovered a few while ago: we'll just fetch it from the tokenSet object together with the token value itself, so that our code will work even if we'll change that in the future.
Alternatively, if you prefer to send the Antiforgery token in the request's HTTP headers, you might want to use the following approach:
1 2 3 4 5 6 7 8 9 |
$.ajax({ type: "POST", url: "https://www.my.url/Controller/Action", headers: { '@tokenSet.HeaderName' : '@tokenSet.RequestToken' } data: { my: "data" }, success: function() { /* do stuff */ } }); |
With any of these neat add-ons, our Ajax POST request will be accepted by the Controller: no more 405 Method Not Allowed errors for us!
Conclusion
That's it, at least for the time being: I hope that this post will help other ASP.NET Core developers that are looking for a way to overcome the 405 Method Not Allowed HTTP error when trying to perform Ajax POST requests within a ASP.NET Core web application that does implement the Antiforgery system.
Where is Context defined for the following statement:
var tokenSet = antiforgery.GetAndStoreTokens(Context);