something I'm struggling to understand about browser security is why you can send cross-origin POST requests with the user's cookies with a form, but the exact same fetch() call won't include the cookies

(I mean it's clear why we would NOT want to include the cookies, but it feels weird that it's allowed in one context but not in another)

i think the answer i'm hearing to this is that form submissions & the fetch() API were just invented at totally different times. When we decided how forms work it was a simpler time and we were more trusting and we're stuck with those decisions now
@b0rk any security rooted in JavaScript implementation like fetch is not safe, anyone can set http headers like a cookie in any kind of http request. Html forms are based on http only, fetch is client side scripting. Just security must be server side primarily

@b0rk Wasn't the fetch API basically created in response to the rise of RESTful APIs and - perhaps more accurately - "ajax" asynchronous browser calls?

Form handling wraps it all up for you during a time when most of the server functionality we take for granted didn't exist - we used to use SSE for "GIF" animation!

So, yeah, simpler time in a lot of ways. HTML was never meant to be a full language, but Javascript changed that expectation.

@b0rk Yep, definitely that. Though it would be interesting to know when exactly cookies started being sent by form posts (I guess always because it's just another http request). It seems both were invented in 1994, cookies by Lou Montulli at Netscape, forms by people making the Viola browser. As a budding web developer in those days (from about 1997) I always used forms but had to really get my head around cookies.

@b0rk Yeah, forms were introduced in HTML 2.0 in 1995. Javascript also came out in 1995, but wasn't really nailed down as a cross-browser spec until 1997.

Up to that point, JS couldn't really do anything fetch-like without a lot of gymnastics. That is, you could send data as query string parameters (commonly for transparent img elements), but it was really hard to get any meaningful data back from that response. It was possible via AJAX (Asynchronous Javascript + XML), commonly via iframe, but not in any way beginner-friendly, nor did it lend itself to 1-liners. It was very brute-force: you dynamically add a script to the DOM, and when the fetched JS ran it then talked to some window variable you'd set up to receive the data.

This was when you really started seeing cross-domain concerns.

In 1999, along came XMLHttpRequest, introduced by MS for Internet Explorer. You wouldn't see fetch until EcmaScript 7 in 2016. They have similar functionality, but vastly improved usability for the latter.

@ricko i keep forgetting how new fetch is
@b0rk i was gonna say cause a form requires a user action but it doesn't actually
@b0rk partly historical reasons and partly that script cannot read the response to a POST submitted via a form. letting it send the request still carries risk though
@b0rk (a long time ago I have had to do cross origin work by making a script construct a <form> and submit it)

@jcoglan it feels like we think allowing this kind of cross-origin POST request is bad but also once we realized that it was bad it was too late to change it

(though I guess browsers are actually evolving the SameSite defaults so things are changing slowly)

@b0rk right, browsers had already allowed cross origin form submission for ages before XHR and same origin script restrictions were added
@b0rk @jcoglan "once we realized that it was bad it was too late to change it" is probably a good summary of the stack as a whole, not just POST requests.
@b0rk @jcoglan cors is so pitiful. this is only one example. who thought silently making an extra, uncacheable OPTIONS request was a good idea?
@wwarner @b0rk I maintain a pub sub messaging library and I've been mad about this for about 15 years at this point :) I also somewhere have a notes file on CORS because it is so baffling, if that would be useful
@b0rk can we get the result that way?
@ellis no, neither of them give you the result, but one of the sends the user's cookies and the other doesn't
@b0rk My guess would be legacy/historical reasons.
@b0rk yeah this trips me up too! reads are more closely guarded than writes in some sense.
@b0rk the why is that CORS was implemented as a hacky fix and nobody made a good version before people stated using it.
@b0rk probably due to historical reasons, and sensitive POST endpoints would need to be protected against CSRF tokens already. Form submissions also redirects, it would not be possible for a different origin to read the response anyway.