Closed egil closed 4 months ago
All requests are going to be executed from HTMX in a full page context so storing the Antiforgery token in the head meta HTMX config section should work fine. I'm not certain why cookies would be needed.
As I understand the anti-forgery request token (ARFT), it is only valid for the first request after it was issued. If it is stored in the head and HTMX is used to issue a requests, the config in the head would not be updated if the result only includes changes to part of the body, and subsequent requests would fail due to an already used AFRT.
It is very likely that there are subtleties to this I have not understood completely. I do think this approach is an alternative to what Khalid is doing where he exposes an endpoint that allows the user to get a new ARFT as needed. However, those require an extra round-trip to the server.
With this solution, it does not matter whether the last load from the server was a regular full page load or a Hx request, and if the next request is a normal page, navigational, form post or an Hx request (Ajax).
So I need to read up on my antiforgery request tokens I guess. I thought as long as the cookie was valid and the logged in user didn't change you would be able to reuse the AFRT for multiple requests. The actual validation took the combination of the cookie token as well as the request token. Something feels off storing the request token along with the cookie token in the same response cookie but I could be wrong. Maybe that's a common practice idk.
I thought the security partly came from having the request token inside the server response somewhere and then the cookie was used to store the cookie token. Then when submitting I pictured it kind of like unlocking a door that had two key holes, except both keys came from different places (one from the cookie, the other embedded in either a header or form). The request token on the server response always matched up with whatever was in the cookie as a pair when requesting as well (again sent via cookies in the header as well as through an additional header). Maybe that separation when distributing the tokens to the client isn't important (?) with signed double submit cookie. I'm not sure what the mindset of the developer was when making the mechanism.
Reading up I do see an example similar to yours on learn.microsoft.com but it seems to limit the endpoint to just the homepage rather than all pages:
var antiforgery = app.Services.GetRequiredService<IAntiforgery>();
app.Use((context, next) =>
{
var requestPath = context.Request.Path.Value;
if (string.Equals(requestPath, "/", StringComparison.OrdinalIgnoreCase)
|| string.Equals(requestPath, "/index.html", StringComparison.OrdinalIgnoreCase))
{
var tokenSet = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokenSet.RequestToken!,
new CookieOptions { HttpOnly = false });
}
return next(context);
});
Found this, which a lot of explanations behind the asp.net antiforgery algorithm seem to reference. This is a worthwhile read: https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html
Thanks for the suggestions. I did look at this part, as it seemed more compatible with this scenario: https://learn.microsoft.com/en-us/aspnet/core/security/anti-request-forgery?view=aspnetcore-8.0#javascript
I want to understand this topic more, and it looks like your owasp article will help, but for now, I won't dive deep into this, other things need attention first. My current take is that it's probably not wrong to set a cookie like I do here since the .NET lib gives you the ability to talk about it in their article.
Enable cross-site scripting protection for HTMX page loads without forms with hidden anti-forgery tokens.
The current implementation ensures a cookie that HTMX can read is always sent on each request. This enables it to pick that up if the request does not already contain the anti-forgery token.
Cookies carry an overhead. An alternative would be to send the token in response headers for Htmx-initiated requests. Then htmx can pull the token from the response. For first-time requests or full-page requests, the token could also be included in the Htmx config written to the head element.
Inspiration: