14 ms·
How to win at CORS
- Animats 5y agoYou can't always access an image across origin boundaries. If you load an IMG into WebGL as a texture, that's not allowed cross-domain. It's considered "processing" the image, rather than just displaying it. I ran into this when displaying slippy maps from map tiles. You can display the map tiles with JavaScript regardless of origin, but use WebGL, and you have to deal with the origin problem.
- jaffathecake 5y agoThe article covers this. It's because WebGL has texture readback, giving you access to the pixel data. Reading image pixels isn't allowed unless the resource passes a CORS check.
- billfruit 5y agoHow they jump into the article without explaining or even expanding what the CORS acronym stands for.
- kindall 5y agoIf you don't already know what CORS is, you're probably not a Web developer and don't need to know.
- Waterluvian 5y agoI think this is misguided reasoning. Everyone starts somewhere, and the hallmark of reasonably competent writing is a ramp-up. Whether it’s one sentence or one chapter. I think failing to define acronyms is a violation of just about any style guide out there. Communication is a majority of engineering so I think these things are very important.
- hsbauauvhabzb 5y agoTo be fair, there are many web devs who have never needed to worry about cross origin resource sharing
- soniczentropy 5y agoHow do you become one of those kind instead of smashing your face against CORS constantly
- adminscoffee 5y agoi think the purpose of cors is to slowly make web devs go insane. but yeah, more coors less cors
- hsbauauvhabzb 5y agoLegacy developers would be the biggest group.
- 5e92cb50239222b 5y agoUsually the same ones who then download plugins like https://addons.mozilla.org/en-US/firefox/addon/access-control-allow-origin/ https://addons.mozilla.org/en-US/firefox/addon/access-contro... because properly configuring the backend is ¨too complicated¨. Been there, done that.
- hsbauauvhabzb 5y agoYou can use the origin announce headers from Firefox to block cors also, unsure if that works with chrome. But I was referring to legacy code (or those whose SPA is stored on the same domain as API endpoints).
- nuerow 5y ago> If you don't already know what CORS is, you're probably not a Web developer and don't need to know. This is an appallingly poor and misguided take, and goes against the most basic rules of writing technical documents. Docs need to be clear, unambiguous, and self-contained. The very first time a acronym is presented, it must be after the full name is presented. It takes less than a sentence to do the right thing. There is no excuse.
- mrweasel 5y agoSometimes OPS people get the job of fixing CORS for developers who don't understand how it works exactly. I work with a client who built a web app in... Vue, I think. For unknown reasons they decided that it would be better that the APIs they need to call to live on the same domain. At the same time, the developers decided that the API microservices should not return CORS headers. Instead it was left to operations to hack in CORS headers in the webserver/loadbalancer.
- codetrotter 5y agoIt means Cross-Origin Resource Sharing. You can read about it at https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS I agree though that it is good practice to say what acronyms mean when using them.
- jaffathecake 5y agoAuthor here! I added a small reference to the acronym in the article, but I don't think it really matters. I actually forgot what it stands for the other week, but it didn't prevent me understanding it. And relearning the acronym didn't help me understand it more.
- billfruit 5y agoThank you. I was just nitpicking, there.
- jaffathecake 5y agoAt least the expansion of the acronym isn't actively harmful to understanding what it is, unlike AJAX, JAMStack etc etc
- humbleMouse 5y agoI’m pretty sure “Cross Origin Request” is extremely helpful for people learning this concept. It literally describes exactly what is happening.
- chrismorgan 5y agoPoint of fairly idle curiosity about the presentation of the article: why do you put a trailing slash on your empty elements (img, link) in your code samples? Some aren’t aware that the trailing slash is useless in the HTML syntax, simply being ignored by the parser and not doing anything. (Except for in inline SVG and MathML content, which switch the parser into a more XML-like mode where the trailing slash behaves as in XML.) I know you’re not in that category. Some hold that the trailing slash should be encouraged because it reminds the reader it’s a void element. I can imagine some recommending it for XML compatibility (which is related to the original purpose of the ignore-the-trailing-slash behaviour, though slightly inverted in direction), but I don’t think I’ve ever encountered anyone saying so. I hold that in HTML syntax the trailing slash is mildly harmful, because I almost never see it applied consistently so that the document wasn’t valid XML syntax anyway (for example, your site’s source has a <link rel="preload" as="font" crossorigin href="/c/logo-font-5449c974.woff2">) and because it misleads people into thinking that it’s a way of closing elements. So yeah, I’m curious, because it looks deliberate. Habit? Reason? :-) (For those that don’t know about the two syntaxes for HTML: XML syntax for HTML is still a thing and probably always will be; load data:application/xhtml+xml,<html%20xmlns="http://www.w3.org/1999/xhtml"/> http://www.w3.org/1999/xhtml"/> in your browser as a starting point for a demonstration.)
- EE84M3i 5y agoFor me, I write the trailing slash because it's easier for me to reason about mentally. It also means I don't have to context switch if I'm writing JSX or XML instead of HTML.
- srgpqt 5y agoFor me, adding the ending slash is just like adding semicolons at the end of javascript statements. They may be optional sometimes, but there is something to be said about consistency and clarity that comes with using them always. Less cognitive overload to boot.
- kadoban 5y agoAren't they optional 100% of the time though?
- jakear 5y agoFor some real fun try accessing a redirect's resource when the redirector requires an Authorization header. You'll need 4 separate round trips, and the final endpoint will need to restrict requests based on Referer in addition to Origin, meaning you need to write all your own preflighting logic.
- 88913527 5y agoI can tell you how to lose at CORS in Chrome. If your browser caches a response, and sometime later you mutate the request by adding the "Origin" header it (e.g, add attribute crossorigin="anonymous" to a <script> tag), Chrome won't make a new request. What it will do is use the cached response, which is missing the ACAO response header, and thus the browser rejects a file from its own cache via draconian security policy. There are many ways to lose at CORS and this one is my story.
- jaffathecake 5y agoAuthor here! The post covers this detail. This happens because your response is missing a Vary header. Getting Vary right isn't just important for Chrome, it's important for CDNs too.
- 88913527 5y agoYou're right, the real issue is CloudFront won't include Origin in the Vary response header if it wasn't included in the initial request. And if you change your HTML attributes, you're changing your request, but you essentially end up with a poisoned local cache. Rolling out crossorigin="anonymous" on previously cached assets is a subtlety you won't know about (even if you think you know CORS) until your site breaks as critical assets are missing.
- jaffathecake 5y agoYeah, it's generally understood that you need to change the URL to cache-bust when the content changes, but it's easy to forget that you need to do the same thing if important headers change. Hmm, I think I'll add a section to the article on this when I'm back at my laptop.
- jaffathecake 5y agoAaaand it's done https://jakearchibald.com/2021/cors/#cors-and-caching https://jakearchibald.com/2021/cors/#cors-and-caching Thanks for prompting this!
- nhumrich 5y agoHow to win at CORS: Don't use it. Just put the apis you need on the same domain, use a reverse proxy. Same-site just works, always.
- jaffathecake 5y agoDon't do this with third party APIs though! It's even riskier than adding a third party script.
- nhumrich 5y agoWhy?
- humbleMouse 5y agoExactly, the whole concept of CORS is just a security issue waiting to happen. Especially these days with all these cloud systems having 14 network layers that all ignore certain headers and add other ones.
- locallost 5y agoMy colleagues and I came to the same conclusion some time ago. I think CORS is not that complicated in essence, but I don't need to deal with it often enough to really memorize all the details. And there are many, as the article shows. Somehow we were always able to in the end deal with it, but at one point we didn't want to anymore. You make all these plans on how this service should be service.example.com and that one service2.example.com and this kind of feels like building something, but in reality it's just a headache unless you really need to do this for some reason.
- fraktl 5y agoThis. But hey, with so many impostors around - they're not capable of fathoming how golden this advice is. Sad truth is, since "developers" don't use this, they genuinely don't understand browsers and HTTP and that's what 's dangerous.
- blacktriangle 5y ago
- thepasswordis 5y agoCORS has been no shortage of greys in my beard! When I'm writing some frontend that is hosted on localhost, with an API that is hosted on its domain somewhere, it always is some sort of PITA to get the dev environ started. There's a plugin for firefox that ignores CORS which is helpful for this. It's becoming less useful for me as my APIs now usually have a toggle to add a cross origin header which allows localhost. Still useful.
- dspillett 5y ago> writing some frontend that is hosted on localhost I assume this would also work for CORS purposes: for some time I've not used localhost where possible, for SSL reasons. Giving the local machine a perfectly valid name that I can get a cert for via LE (or already have a cert for, I actually use a non-production name for which I maintain a wildcard cert) is slightly less faf than having my own signing cert installed as trusted everywhere I might need it. Anything I might do publicly is HTTPS-only so my dev/test environments are too.
- eurasiantiger 5y agoJust use http-proxy to set up a local domain so you can access your dev environment at frontend.yoursite.local (proxied to localhost:3000) and the api at api.yoursite.local (proxied to whatever 3rd party api). Boom, problem solved. You can even rewrite headers and content of the requests in and out.
- jnovacho 5y agoDo you have any recomendations, which proxy to use? Specifically on Win.
- eurasiantiger 5y agohttps://www.npmjs.com/package/http-proxy https://www.npmjs.com/package/http-proxy Integrates nicely with existing JS frontend tooling.
- roca 5y agoI tried really hard to get <video> and <audio> to default to requiring same-origin but people were still skeptical about CORS deployment, and there were also arguments for consistency with <img>. Oh well, I think we eventually got to a consensus that that "consistency" is not worth having.
- EE84M3i 5y agowebfonts require it though! I was really surprised!
- roca 5y agoYeah, when Web fonts rolled around we fought that battle again and won. Some important people saw the light :-). (And to be fair, CORS was more widely available on servers.)
- jaffathecake 5y agoI think font foundries were particularly keen on that juicy Origin header so they could tell who was using their font, and block sites that hadn't paid.
- roca 5y agoThat is also true.
- lloydatkinson 5y agoI am really glad they overruled you.
- jaffathecake 5y agoI think you folks were in a really tricky spot. Making <audio>/<video> require CORS would have been the right decision for security reasons, particularly since <audio>/<video> introduces range requests. It would have prevented these security bugs https://jakearchibald.com/2018/i-discovered-a-browser-bug/ https://jakearchibald.com/2018/i-discovered-a-browser-bug/. However, the competition at the time was Flash, and making <audio>/<video> so much harder than it was with Flash would have put developers off. Fwiw, I regret that opaque responses can go into the service worker cache, since it caused quota-sniffing issues that we had to work around. But, if we didn't allow it, it would have been a feature regression vs appcache. sigh
- pachico 5y agoRather than adding my own personal experience in the matter, I want to do gratulate the author for the article. It's engaging and even entertaining. Very well done!
- lloydatkinson 5y agoCORS is a stupid idea that serves no purpose. If someone is really determined they will either 1) turn off CORS with a browser extension 2) simply call your precious API from something other than an a browser It is essentially security by obscurity and protects nothing. Don't get me started how some technologies like AWS Lambda with a Gateway, when a function has an error, responds by default in such a way it makes the browser log a CORS error instead of, you know, a 500 or the actual error message. I always do the "allow anything" setting for CORS when I make an API.
- fraktl 5y agoOut of curiosity, how come you dared to make this comment without ensuring you've got all your facts straight? No, it isn't security through obscurity. Yes, it protects and it protects a lot. Thank you for contributing to lack of knowledge, do keep up.
- lloydatkinson 5y agoYou're welcome
- jaffathecake 5y agoAuthor here! I don't know if you read the article, but it includes the history and reason behind CORS. It definitely protects more than nothing :)
- lloydatkinson 5y agoI had a quick scan yeah. I'm sure historically it does but I've yet to ever need or encounter anyone that makes use of these features. I've never been in a team that particularly cares about any of it's supposed value either, it's merely a frustration to remove...
- jaffathecake 5y agoYou should try out some connected home devices, including things like routers. Like I said in the article, many of them assume they're safe because they're on a local network, so their security is lax. Also, I know quite a few public sites that serve debugging data if the request comes from the company's IP range. They shouldn't be doing this, but they do. Maybe one day we can remove CORS for no-credential requests if we can detect that the destination isn't "internal", and we just decide that folks who serve debugging data by IP deserve to have their data leak. I've heard ideas around this for 10 years now, but maybe it'll happen eventually.
- jaffathecake 5y agoBonus fact: I wanted the 'app' (https://jakearchibald.com/2021/cors/playground/ https://jakearchibald.com/2021/cors/playground/) to allow the HTTP method to be set to anything, which meant I needed a server that could accept anything. I usually use NodeJS, but it turns out the HTTP library they use turns the HTTP method into an enum, so only a subset is supported (https://github.com/nodejs/node/blob/d798de1c653efa5ec0015d44f266db6dda197bf4/deps/llhttp/include/llhttp.h#L121 https://github.com/nodejs/node/blob/d798de1c653efa5ec0015d44...). This restriction only exists in their HTTP/1 library, their HTTP/2 library supports any method. Anyway, I couldn't use that, so I used Deno via Deno Deploy. Their HTTP library supports any method, and the APIs they use are very similar to web APIs, so it was really easy to get started. Here's the server code: https://github.com/jakearchibald/cors-playground/blob/main/index.ts https://github.com/jakearchibald/cors-playground/blob/main/i....
- chrismorgan 5y agoMy recollection from circa 2013 is that Node.js at least used to use the nginx HTTP parser, which was a horror of manually-implemented state machine written so in the name of performance, but consequently basically unmaintainable and fairly bug-riddled. And not as fast as it should have been, anyway. (The state machine approach is fine, but it should have used a lot more code generation.) It read the method byte by byte into the state machine, and baulked at unknown methods. Evidently they’ve kept that limitation, whatever they may have changed since in the parser they use (and I think nginx did eventually abandon and replace that parser entirely). (These are my recollections from investigation I did back in 2013 when I was writing the first serious Rust HTTP library.)
- jaffathecake 5y agoWhoa, I didn't know that! But yeah, it seems like https://github.com/nodejs/http-parser https://github.com/nodejs/http-parser is based on nginx. It now uses https://github.com/nodejs/llhttp https://github.com/nodejs/llhttp but has some of the same legacy. On the other hand, deno's HTTP stuff is built on top of Hyper, a Rust library https://github.com/hyperium/hyper https://github.com/hyperium/hyper
- 5y ago
- qubyte 5y agoThis is the resource I wish I'd had some years ago when I was starting out as a server dev. I remember the (lack of a) Vary header in particular causing me hours of frustration.
- lwouis 5y agoWhen developing a webapp these days, I use a local proxy. This allows me to type a staging/production URL into Chrome, and get the frontend and the backend from either my local machine or staging/prod. I can mix and match any combination by checking/unchecking a box in Proxyman. This means there is no need to whitelist localhost for CORS, and other hoops. Another advantage is that you're experiencing the app with SSL, so you may notice bugs that you would miss if you're used to work with HTTP locally. I've had these bugs which "only happen on production" in the past, and it's a nasty thing to deal with because it will be in a rush, since it happens as a surprise, and impacts users immediately. It can also bypass QA if the QA environment is also using workarounds and not a prod-like setup with SSL and the likes. I recommend giving it a try. It's a workflow I haven't seen promoted anywhere before.
- raihansaputra 5y agoThanks for the tip for Proxyman!
- tomashubelbauer 5y agoI tried to achieve the same thing in the past and have run across issues with HSTS. The details are escaping me but I think it might have been that when using the production app without a proxy, the SSL certificate was associated with the HSTS records in the browser and when I switched to a proxy, the HSTS started failing because the certificate has changed. Have you run into this at all? How have you solved it?
- llimllib 5y agoI don't develop at the same URL as I publish at - I'll generate keys with mkcert and host my site locally via a proxy at local.realdomainhere.com for a site that has dev.realdomainhere.com and the prod domain www.realdomainhere.com.
- tomashubelbauer 5y agoI see, I misunderstood. My dream setup is one where I have the same URL for TEST/STAG/PROD and just switch which BE the URL resolves to based on some configuration/tool.
- pkpioneer 5y agoHow to Win Every Game in Netflix’s ‘Squid Game’: https://pkpioneer.blogspot.com/2021/10/how-to-beat-every-game-in-netflixs.html https://pkpioneer.blogspot.com/2021/10/how-to-beat-every-gam...
- Aulig 5y agoI wish there was a way to let me make POST requests on behalf of the user without dealing with CORS. Of course the request wouldn't include any user cookies etc to avoid the security issues. I just want to easily let my users make API requests to an API whose CORS settings don't allow it. Instead I have to tell my users to give me their API key so I can make the request from my server. Or I'd have to tell them to run a program that makes the requests. Both options suck for non-technical users.
- jaffathecake 5y ago> I wish there was a way to let me make POST requests on behalf of the user without dealing with CORS. Of course the request wouldn't include any user cookies etc to avoid the security issues. You can! And it can include credentials! You can do this with a basic <form> element, so fetch() lets you do the same. What you can't do is read the response. Demo: https://jakearchibald.com/2021/cors/playground/?prefillForm=1&requestMethod=POST&requestUseCORS=&requestSendCredentials=1&preflightStatus=406&preflightAllowOrigin=&preflightAllowCredentials=&preflightAllowMethods=&preflightAllowHeaders=&responseAllowOrigin=&responseAllowCredentials=&responseExposeHeaders=&requestHeaderName=Content-Type&requestHeaderValue=multipart%2Fform-data https://jakearchibald.com/2021/cors/playground/?prefillForm=...
- Aulig 5y agoThank you! Unfortunately I need to read the response (so extra thank you for pointing that out before), but maybe just posting will be enough for a future application :)
- AllThatJazz 5y agoEarlier this year, the feds signed an agreement that let Boeing executives off the hook for the 737 MAX catastrophes, which killed 346 people. The lead prosecutor, Erin Nealy Cox, then took a job with the firm that leads Boeing's criminal defense.
- rogual 5y agoIf CORS is supposed to solve the problem of credentials being sent to third-party sites without the user's knowledge, I don't understand why the solution wasn't just to not send the credentials.
- jaffathecake 5y agoThat isn't all it's supposed to solve. Give the article a read, these points are specifically addressed :)
- rogual 5y agoEnlightening, thanks! I had thought maybe a site could in theory use your IP or even just your ability to connect to figure out who it thinks you are, but didn't know this was so common.
- ufmace 5y agoI've banged my head against some CORS puzzles recently. That's a pretty good guide, but I actually know an extra quirk that was missed: You know that nice "Access-Control-Allow-Credentials: true" header? In theory, it means that you can make authorized requests with cookies included to the cross-origin API. It actually has some extra rules that aren't obvious though. The cookies won't actually send unless they explicitly have "SameSite=None" set. Which itself isn't valid unless you are both making the request over HTTPS, and the cookie also has the flag "Secure". See here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Se... The cookie logic is kind of mysterious. When everything is set up correctly and it works, it's pretty invisible to the code making the cross-origin requests. But if it isn't set up quite right, it will just not work, and it can be pretty tricky to figure out what's wrong. The whole SameSite logic is a pretty interesting kludge too. Ideally, most things would be SameSite=Strict, but that also means that links the user follows from third-parties to your site won't include cookies, since in theory those requests could include GET params doing who knows what. Digging into all of the CORS rules and how they interact is almost an archaeology project into how the web was built and all of the weird things that both legit sites and malicious attackers have tried to do over the years.
- jaffathecake 5y agoThe article mentions the SameSite stuff early on, then kinda mentions it in passing when it comes to CORS + credentials "The same-site rules around cookies still apply, as do the kinds of isolation we see in Firefox and Safari. But these only come into effect cross-site, not cross-origin". Seems like I need to word it better though.
- Steltek 5y agoIs it fair to say that the strongest CORS request allows the weakest? That is, if your server supports a preflighted, credentialed, cached CORS request with weird headers and methods, then it would support just about anything? Perhaps a detailed walkthrough of that one specific scenario, which would enable almost all others, would be helpful.
- nerdkid93 5y ago> remember, the 's' in 'IoT' stands for security I don't know why, but this cracked me up. :D
- hbn 5y agoWorth noting that it's a fairly common saying, not something he came up with for the article
- ncc-erik 5y agoVery comprehensive blog, nice work. As a pentester, I always get excited when I see ACAO or an OPTIONS request in my proxy logs. It's still really hard to wrangle and get right.
- derekzhouzhen 5y agoThe problem with CORS is that most people do not understand it and will not bother the change anything to make life easier for other developers. At least, static files open to the public shall have 'Access-Control-Allow-Origin' set to '*' by default. Vercel and github page do that already which is nice. Most other web host don't, Not even Netlify, who supposedly advocates for the JAMStack. For dynamically generated content, the site runner should also consider open up if the content is for public consumption and not varying by user's credential.
- skoskie 5y agoDude, writing out the background and history of everything must have taken quite some time, but I found it very valuable in helping to understand the present state of things. I just want to say thank you.