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

CORS user experience: htmx request headers hx-* not allowed by Access-Control-Allow-Headers in preflight response. #779

Open
gnat opened this issue Jan 22, 2022 · 10 comments

Comments

@gnat
Copy link
Contributor

gnat commented Jan 22, 2022

So this issue is currently solvable, but the user experience around it sucks.

Issue

This involves requests to external 3rd party API's. htmx works wonderfully with 3rd party API's, even with CORS restrictions, as long as the custom hx-* headers are stripped out of the request.

Briefly (yet incorrectly?) touched upon here: https://github.com/bigskysoftware/htmx/blob/master/www/extensions/client-side-templates.md?plain=1#L89

Root of the problem is the target servers CORS policy will reject your request if anything but standard headers are sent (ex: content-type). So it's a non-starter to send any hx-* headers with your request

Suggestion

Maybe an hx-headers-off property that turns off extra hx-* request headers, so people can easily talk with 3rd party APIs? Very much open to discussion for the best htmx experience.

Temporary Solution

You can get the correct result by using something like this under your htmx form:

<script>
// Remove all extra htmx headers, re-add content-type.
document.body.addEventListener('htmx:configRequest', function(event) {
    event.detail.headers = ''
    event.detail.headers['Content-Type'] = "application/x-www-form-urlencoded; charset=UTF-8"
});
</script>

Then you can successfully call 3rd party API's with restrictive CORS in <form hx-post="https://example.com/api"> .... without getting CORS rejections such as:
Access to XMLHttpRequest at 'https://example.com/api' from origin 'http://localhost' has been blocked by CORS policy: Request header field hx-target is not allowed by Access-Control-Allow-Headers in preflight response.

Open to suggestions and feedback.

@jreviews
Copy link
Contributor

I ran into a similar issue a while back where the preflight requests were blocked due to the server's CORS policy. In my case deleting the headers completely removes the pre-flight requests:

document.addEventListener('htmx:configRequest', (evt) => {
    evt.detail.headers = [];
});

@bennettscience
Copy link

Removing the headers is only part of a fuller solution. The upload object on XHR requests can also trigger a preflight check which can fail in some cases with third party APIs.

Using htmx:configRequest doesn't give access to the xhr object to remove upload, so I've tried making the change using htmx:beforeRequest and htmx:beforeSend, but it's still being included. It may be nice to add a couple of configuration flags like hx-no-upload or even an hx-bare option which send as little as possible.

@SumitBando
Copy link

SumitBando commented Apr 21, 2023

In addition, Chrome now adds Referer: strict-origin-when-cross-origin by default:
https://developer.chrome.com/blog/referrer-policy-new-chrome-default/

HTMX code seems to refuse to allow
<script>
event.detail.headers['Referer'] = "no-referrer"
</script>
making referring to 3rd party content impossible.

@gnat
Copy link
Contributor Author

gnat commented Apr 21, 2023

Wanted to add, since htmx 1.9.0 hx-on (https://htmx.org/attributes/hx-on/) offers a more concise developer experience to ensure your headers are compatible with the CORS settings of your target server, on specific elements.

<div hx-on="htmx:configRequest: event.detail.headers=''; event.detail.headers['Content-Type']='application/x-www-form-urlencoded; charset=UTF-8'" />

@james0r
Copy link

james0r commented Jun 22, 2023

Damn just like 8 hours of my life to this.

Would be nice if there was a more eloquent way to overcome this.

@ewurch
Copy link

ewurch commented Jul 20, 2023

I spent a few hours trying to solve a similar problem, in my case it was a form submitting data to the backend and being redirected to another website as a response (to be more specific, to a stripe checkout session).

The issue was the hx-boost="true" attribute that I added to the body tag.

Solution was to not allow the hx-boost effect on the form:

 <form hx-boost="false" action="{% url 'create_checkout_session' %}" method="POST">
...
</form>

@individuwill
Copy link

Just ran into this issue as well. I initially used the trick of clearing the headers in the event callback as suggested already in this thread.

However, I then came across the documentation on the hx-request attribute here https://htmx.org/attributes/hx-request/ which allows you to disable the headers with an attribute set like this hx-request='{"noHeaders": true}'. With this attribute set I no longer need to clear the headers in the callback.

This fixes the issue for me in Chrome, but I still have a CORS issue in Firefox because of the event handler registration on xhr.upload

@instagregt
Copy link

Just ran into this issue as well. I initially used the trick of clearing the headers in the event callback as suggested already in this thread.

However, I then came across the documentation on the hx-request attribute here https://htmx.org/attributes/hx-request/ which allows you to disable the headers with an attribute set like this hx-request='{"noHeaders": true}'. With this attribute set I no longer need to clear the headers in the callback.

This fixes the issue for me in Chrome, but I still have a CORS issue in Firefox because of the event handler registration on xhr.upload

When I add this the request that previously worked gives 400 bad request. It changes the way the request is done (form data vs. parameters). Is there any way to do this that maintains the default behaviour?

@individuwill
Copy link

Just ran into this issue as well. I initially used the trick of clearing the headers in the event callback as suggested already in this thread.
However, I then came across the documentation on the hx-request attribute here https://htmx.org/attributes/hx-request/ which allows you to disable the headers with an attribute set like this hx-request='{"noHeaders": true}'. With this attribute set I no longer need to clear the headers in the callback.
This fixes the issue for me in Chrome, but I still have a CORS issue in Firefox because of the event handler registration on xhr.upload

When I add this the request that previously worked gives 400 bad request. It changes the way the request is done (form data vs. parameters). Is there any way to do this that maintains the default behaviour?

If your request was already working, I'm not sure I understand the use case for adding that option. It seems that most in this issue thread had an issue, and adding the option (or removing the headers) helps to resolve it.

Since it was a 400 bad request, the request did make it to the server and was not blocked because of CORS (and since it was working before it wasn't blocked because of CORS). I suspect that the server code is expecting a particular header to be set on the request. There are a set of default headers that are sent as documented here: https://htmx.org/reference/#request_headers . Using the no-headers option strips all of those headers from the request.

If you use the no-headers option AND try to set a header as documented here: https://htmx.org/attributes/hx-headers/ the set header will be stripped, and not sent to the server. So using the no-headers option is an all or nothing deal.

Another feature that may be impacted, though I think this is less likely, is the sending of credentials option with the request. This feature is enabled if at least one of the following cases is true

  • when hx-request option credentials is set as documented here https://htmx.org/attributes/hx-request
  • htmx is globally configured to send credentials (disabled by default) with the htmx.config.withCredentials configuration option as documented here: https://htmx.org/reference/#config
  • or one other condition which I haven't figured out in the code. (some reference to an etc object)

Hope that helps!

@bennyzen
Copy link

However, I then came across the documentation on the hx-request attribute here https://htmx.org/attributes/hx-request/ which allows you to disable the headers with an attribute set like this hx-request='{"noHeaders": true}'. With this attribute set I no longer need to clear the headers in the callback.

Thank you so much for this solution. And yes, this has to be clearly documented, as I've just spent two hours running into CORS issues trying to fetch some data from an API.

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