HTMX is a JavaScript library designed to work with various HTTP methods: GET, POST, PUT, and DELETE. Three out of the four methods, excluding the GET method, are meant to cause side effects and mutations on the server: update a shopping cart, complete an order, upload a file, etc. Endpoints that perform modifications are also vulnerable to cross-site request forgery attacks. A malicious party may cause a trusted user to submit information on their behalf to trigger an unwanted action. For example, a hacker may target an insecure form on a banking site, trying to get bank members to transfer funds inadvertently. Hackers can embed these insecure forms in spam emails, dangerous websites, or part of some malware attack.

The safest approach to mitigate these attacks is to include an anti-forgery request token with every request. These tokens aren’t secret, as much as you can only see them as a trusted individual. Then, the application verifies the token using a combination of cookies, form inputs, and header values. It is a technique found in many technology stacks, including ASP.NET Core.

HTMX users may be having issues with the anti-forgery onPOST, PUT, and DELETE calls. Of course, the solution is to pass the token, but how do we do that? In this post, we’ll see how to do just that with HTMX.NET and a few lines of JavaScript.

Note: All examples will be using HTMX.NET Tag Helpers

The No-Additional-Code Solution

If you’re a regular HTMX user, a simple technique to always pass the anti-forgery request token is to use ASP.NET Core’s form tag helper. ASP.NET Core will process all form elements on a Razor page and inject a hidden input element by default. So, instead of adding hx attributes on a button or a div element, try creating a form with the HTMX attributes, and a submit button.

<form hx-post hx-action="Index" hx-controller="Home" >
    <button type="submit">Save</button>
<!-- Request Token will render at the end of form -->
</form>

This solution has some limitations, as you’ll have to wrap every interaction in a form element, thus making it challenging to define interactions as your application grows.

HTMX.NET Configuration and JavaScript Solution

The HTMX.NET library can directly inject the current anti-forgery request token into the htmx.config object, giving you access to the token required to validate a POST, PUT, and DELETE call.

The first step, is to install the HTMX.NET TagHelpers into your Razor _ViewImports.cshtml configuration.

@addTagHelper *, Htmx.TagHelpers

After you’ve installed the tag helpers, you’ll need to add the meta tag to your _Layout.cshtml, and be sure to set the includeAspNetAntiforgeryToken attribute to true.

<meta name="htmx-config" includeAspNetAntiforgeryToken="true" />

Finally, add the following JavaScript to your pages. You can add it to each page or a globally included js file.

document.addEventListener("htmx:configRequest", (evt) => {
    let httpVerb = evt.detail.verb.toUpperCase();
    if (httpVerb === 'GET') return;
    
    let antiForgery = htmx.config.antiForgery;

    if (antiForgery) {
        
        // already specified on form, short circuit
        if (evt.detail.parameters[antiForgery.formFieldName])
            return;
        
        if (antiForgery.headerName) {
            evt.detail.headers[antiForgery.headerName]
                = antiForgery.requestToken;
        } else {
             evt.detail.parameters[antiForgery.formFieldName]
                = antiForgery.requestToken;
        }
    }
});

The JavaScript event listener will trigger each HTMX request. Except for GET requests, all requests will have the request token added to the payload if not already present in the parameters collection.

There you have it! Now you can trigger all HTMX requests while also passing the anti-forgery tokens.

I hope you found this post helpful, and thank you for reading.