Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support automatically adding CSRF token when using the custom processors #36

Open
Paddy-Farmeye opened this issue Nov 29, 2022 · 15 comments

Comments

@Paddy-Farmeye
Copy link

Paddy-Farmeye commented Nov 29, 2022

When submitting forms, Thymeleaf's th:action attribute adds required CSRF tokens automatically. It seems to me that this feature isn't supported when using, for example hx:post on a form.

For reference, it would eliminate the need to manually add <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" /> to all of my newly-converted-to htmx forms.

Is this something that you envisage will be supported?

@checketts
Copy link
Collaborator

That seems like a worthwhile feature. With th:action Thymeleaf is generating the hidden input right? Would you expect the hx:post to do similarly? Or Perhaps use another HTMX feature to include the CSRF token?

@Paddy-Farmeye
Copy link
Author

More information can be found at: https://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.html#advanced-integration-features. But basically, Thymeleaf's Spring integration utilizes the RequestDataValueProcessor to transparently add hidden fields to "enable security features like e.g. protection agains CSRF (Cross-Site Request Forgery)."

th:action calls RequestDataValueProcessor.processAction(...) before rendering the form’s action attribute, and additionally it detects when this attribute is being applied on a form tag —which should be the only place, anyway—, and in such case calls RequestDataValueProcessor.getExtraHiddenFields(...) and adds the returned hidden fields just before the closing form tag.

@wimdeblauwe
Copy link
Owner

Would be nice to have this indeed!

@Paddy-Farmeye
Copy link
Author

@wimdeblauwe do you see this feature being on this library's roadmap in the near future or...?

@wimdeblauwe
Copy link
Owner

If I would know how to do it, I would love to add it. I now asked thymeleaf/thymeleaf#934 if somebody might now the way how to do it.

@checketts
Copy link
Collaborator

Or the attribute process could add an extra hx-vals attribute that includes the CRSF token is another approach.

@vrish88
Copy link

vrish88 commented Jan 2, 2023

This could also be accomplished in htmx itself by adding an eventListener that adds the csrf token to the request. The spring security docs document how this might be done with a js library.

For HTMX it could be done with something like this (please forgive me, I haven't actually run this code):

<head>
  <meta name="_csrf" th:content="${_csrf.token}"/>
  <script>
    document.body.addEventListener('htmx:configRequest', function(evt) {
      evt.detail.headers['X-XSRF-TOKEN'] = document.querySelector('meta[name="_csrf"]').content
    });
  </script>
</head>

This would cover form submissions as well as requests triggered by the attributes hx-post, hx-get, hx-put, etc.

@TomBeckett
Copy link

I'm trying @vrish88's approach right now and it does look promising.

However, for some reason the CSRF token being sent is not the same token as the server expects in the CsrfFilter.class.

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
  // snip to line 61
  CsrfToken csrfToken = deferredCsrfToken.get();
  String actualToken = this.requestHandler.resolveCsrfTokenValue(request, csrfToken);

Also minor note, the token header name is slightly different for me (Spring Security 6).

It's also worth inserting the name into the header as this should adapt to Security config changes.

<head>
    <script src="https://unpkg.com/[email protected]"></script>
    <meta name="_csrf" th:content="${_csrf.token}"/>
    <meta name="_csrf_header" th:content="${_csrf.headerName}"/>
    <script>
    document.addEventListener("DOMContentLoaded", function() {
        var token = document.querySelector('meta[name="_csrf"]').content;
        var headerName = document.querySelector('meta[name="_csrf_header"]').content;
        document.body.addEventListener('htmx:configRequest', function(evt) {
          evt.detail.headers[headerName] = token;
        });
    });
    </script>
</head>

The token being sent automatically by Thymeleaf works, so something is clearly missing from this approach.

@checketts
Copy link
Collaborator

Is the CSRF token perhaps changing on different requests? Maybe each request needs to send along a new CSRF as a HX-Trigger that gets pulled in? Is the CSRF token static for an entire session or generated new with each request?

@shimikano
Copy link

Just for reference, in the spirit of the above javascript solutions to set the CSRF header for every htmx request, I've been using this:

<!--/* add the CSRF header to all htmx requests */-->
<script th:inline="javascript">
  /*<![CDATA[*/

  document.body.addEventListener('htmx:configRequest', (event) => {
    const csrfHeader = /*[[${_csrf.headerName}]]*/ 'X-Sample-CSRF-Header';
    const csrfToken = /*[[${_csrf.token}]]*/ 'sample-csrf-token';

    event.detail.headers[csrfHeader] = csrfToken;
  });

  /*]]>*/
</script>

This lets Thymeleaf inject the CSRF header name and token directly into the javascript template, as opposed to meta tags.

Works with Spring Boot 3.

@wimdeblauwe wimdeblauwe changed the title Invalid CSRF token found ... Support automatically adding CSRF token when using the custom processors Jun 4, 2023
@dsyer
Copy link
Contributor

dsyer commented Jul 20, 2023

I'm pretty sure HTMX just sends all the form inputs (including the thymeleaf-generated ones), so as long as hx-post is inside a form it should just work? If you are converting forms to divs, then that's not very semantic HTML. I can see there might be a need for the occasional hx-post outside a form, but it should be the exception, right?

@wimdeblauwe
Copy link
Owner

A button that just triggers a delete of something might be in many cases not part of a form, but just an hx-delete on the button itself.

@dsyer
Copy link
Contributor

dsyer commented Jul 21, 2023

That's a good example. If it isn't in a form, you can add the csrf token manually I guess, and tell HTMX about it with the hx-include. Personally I would do that, rather then getting into the weeds with JavaScript, but it's a matter of taste.

@taypo
Copy link

taypo commented Jul 26, 2024

This is how I did it:

<button hx:vals="${ {_csrf.parameterName: _csrf.token } }" hx-post="...">Publish</button>

@patrickdronk
Copy link

I see a lot of solutions, but most of them are with Thymeleaf. What if you don't use Thymeleaf?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants