egil / Htmxor

Supercharges Blazor static server side rendering (SSR) by seamlessly integrating the Htmx.org frontend library.
MIT License
109 stars 12 forks source link

Cross site request forgery - where should the token be placed? #56

Open egil opened 2 months ago

egil commented 2 months ago

While investigating #50, I discovered that (output) caching will not be enabled if cookies are set.

This is relevant because current Htmxor embeds the CSRF token in a cookie, which htmxor.js then attaches to HX requests, if a CSRF token is not already embedded in the form data.

However, this is probably not the right approach long term, it does influence caching, and may not be the correct approach from a security perspective.

The current model Blazor follows is that forms should include a hidden input field via the <AntiforgeryToken /> component, or by using the <EditForm> component. Each time a form is rendered, Blazor will update the token, even though it can be reused.

We could simply switch to a model where users explicitly have to include the csrf token manually as needed in, but that is tedious at best.

Here are the options I see:

  1. Have the renderer automatically add a hxor-csrf-token="xxxx" attribute on every element that has a hx-post, hx-put, hx-patch, hx-delete attribute. This adds quite a lot of noise to the markup, especially since the token is 60+ chars long.

  2. A variation of this would be to only add the hxor-csrf-token attribute on the top level element being rendered, and let it be inherited down to any child elements.

In both cases, htmxor.js will intercept calls and ensure that the tokens value is correctly included in requests.

  1. Have a <meta id="hxor-csrf-token" name="hxor-csrf-token" value="XXX"> element that gets updated using OOB during each request that includes element a hx-post, hx-put, hx-patch, hx-delete attribute. This would add a minimum noise the markup of the page, but will instead add that noise in the request response.

Neither of the solutions can depend on htmxor intercepting the response from the server and string the token, since it may not be htmx that initiated the request. That was also the reason I decided to use cookies in my first attempt. All three solutions mentioned above would work with both normal and direct requests, but adding a custom header that htmx would have to extract and store somewhere in the DOM would not.