Harden Anti-Forgery Tokens with IAntiforgeryAdditionalDataProvider in ASP.NET Core

Anti-Forgery tokens are a common technique to prevent Cross-Site Request Forgery (XSRF/CSRF) attacks. ASP.NET Core uses a hidden field to store the anti-forgery token and uses the ValidateAntiForgeryToken attribute to validate the token. As the token is sent to the browser in a hidden field, it is also stored in an HttpOnly cookie. When the form is submitted, ASP.NET Core compares the hidden field value with the cookie value and rejects the HTTP request if it doesn't match.
To learn how to use the out of the box anti-forgery technique, read the Microsoft Documentation: "Prevent Cross-Site Request Forgery (XSRF/CSRF) attacks in ASP.NET Core".
Hardening anti-forgery tokens #
A couple of years ago, an external company scanned many of our products and websites for vulnerabilities. One of the issues that arose many times was the following: "The CSRF token in the body is validated on server side but is not revoked after use even though the server generates a new CSRF token."
With other words, the anti-forgery tokens generated by ASP.NET Core can be reused multiple times which increases the risk if your anti-forgery tokens are somehow leaked.
Luckily, ASP.NET Core provides a way to extend the anti-forgery tokens using IAntiforgeryAdditionalDataProvider.
As the interface's name suggests, you can implement this interface to provide additional data to your anti-forgery token which you can validate on form submission. Here's a dummy implementation:
In GetAdditionalData you can generate whatever you want to add to the anti-forgery token. You also receive the HttpContext as a parameter so you can use advantage of the properties on the context such as Request, Session, Cookies, etc.
In ValidateAdditionalData you put in the logic to validate your additional data. If invalid, return false; if valid return true.
For ASP.NET Core to start using your implementation, you need to register it with the dependency injection in Startup.ConfigureServices like this:
You can find the source code for these sample on this GitHub repository.
With this extensibility point, you can resolve the security issue raised earlier in many ways. For example, you can generate a random token and store the token in Session. When the token is validated, you can immediately remove the token from session as you can see in the example below:
The advantage is that you can only use the token once and it will be invalidated. The disadvantage is that you cannot open multiple forms at the same time. Every time a form is requested, a new token is generated and overrides the previous token. Only the latest request form will pass the anti-forgery token validation.
This could be a potential UX nightmare.
To allow multiple forms to be requested and submitted, you could store multiple generated additional tokens. The following implementation stores a comma-separated list in Session. To prevent the session variable from becoming to large, the list is limited to 3 items in this case. When the list becomes too large, the oldest token is removed from the list. When a token is submitted for validation, that token is removed from the list.
Here is the source:
You could also store an expiration time as additional data. On validation, you can see if the time has expired or not. Unless you are a sadist, just make sure the expiration time is sufficiently long like multiple hours.
The following example expires the token within 10 seconds for testing purposes:
Summary #
Using IAntiforgeryAdditionalDataProvider you can harden ASP.NET Core's anti-forgery token feature by adding additional data and validating the additional data.
This security scan prompted me to ask this question on StackOverflow which I ended up answering myself. If this helped you out, feel free to upvote the question and answer.