from Hacker News

You don't need that CORS request

by olingern on 1/3/22, 3:54 AM with 243 comments

  • by igammarays on 1/3/22, 2:40 PM

    You don't even need an SPA. Having the backend + frontend share the same state makes development so much faster and easier it's shockingly refreshing. When building a SaaS project last year I decided to just ditch React/Angular/Vue and did everything server-side like it was 2005. I was extremely pleased at the result -- everything feels snappy, secure, and native for the browser. I was shocked at how much time and headache I saved: not having to design/deploy an API and all the trimmings (ACLs, CORS, JWTs etc.), not having to spend X hours a day wrestling with Webpack/hot reloading/npm/tree-shaking/state management, having direct access to backend secrets/state/APIs and just spitting out HTML, it's just so....lovely. It simplifies performance tuning as well, the only thing I need to optimize is the server, which is easy to do because I have 100% control and visibility, compared to debugging/optimizing JavaScript for a million different clients.
  • by toomim on 1/3/22, 6:12 AM

    CORS is such an ugly web standard. It's not only a pain in you butt, but it also makes your requests slow through these extra pre-flight round-trips.

    I have hope that we can remove it, though, in new versions of HTTP.

    CORS only exists because XMLHTTPRequest broke the assumptions of web 1.0 servers. Suddenly any web browser loading any page anywhere could make a request to your server without the user's explicit permission, and a ton of web servers had already been built and were running under the assumption that users would only hit their endpoints by explicitly typing in a URL or clicking on a link.

    But each time we design a new version of HTTP, we get an opportunity to remove CORS restrictions for it, because there aren't any servers running the new version yet.

    And with Braid (https://braid.org), we're making changes that are big enough that I think it really warrants taking a fresh look at all of this stuff. So I have hope that we can eliminate CORS and all this ugliness.

  • by herpderperator on 1/3/22, 7:41 AM

    The irony in this is that with CloudFront at least, it can't strip the "/api" prefix before sending it to your backend, so you have to have your backend server understand /api/xyz rather than just /xyz calls like when it was on a dedicated api subdomain.

    But don't worry! AWS thought of this! They invented Another Cloud Thing, namely Lambda@Edge, to solve this. Now you can run a JS function for every single request that hits your CloudFront Distribution so that you can run logic in there to strip the prefix before it gets passed to your origin. You read that right! Execute code every time a request hits your proxy! But wait, doesn't executing code for every single request sound insane AND doesn't it also add latency which this was trying to remove? Yes! Isn't cloud just lovely?

  • by Ralo on 1/3/22, 8:21 AM

    Recently chrome required COOP and COEP for shared memory access, which essentially restricts any access to anything besides the same origin. This basically killed my project/motivation of 2~ years.

    Using Emscripten and threads, you require COOP & COEP headers to be sent via the main document. This is not common practice for static html hosting sites, thus requiring that you have access to a config file or .htaccess, and requires ALL assets to be hosted exclusively on that same server.

    Killing my project is a bit extreme, but killed my motivation.

    It's a web-based game with multiplayer that I was hoping people would modify and expand on, hosting themselves, uploading to places like Github.

    I've recently started modifying Emscripten's runtime library to try and treat each webworker as a separate instance that just communicates between each other. This has major overhead as each thread is a new memory instance. I've tried getting it to extract each function into it's own module for a webworker to load but that's a major task.

    Web apps want to act as desktop applications but they're so held back by security it's nearly impossible. We don't even have a proper local storage system.

    To quote Dilbert, "Security is more important than usability. In a perfect world, no one would be able to use anything."

  • by miyuru on 1/3/22, 5:45 AM

    What is the author trying to say, it is very hard to recognize the answer of the article.

    Is it to point requests to sub path of the main domain to reduce OPTIONS requests? eg: api.example.com ---> example.com/api

    In that case why not use proper "Access-Control-Max-Age" headers?

    https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Ac...

  • by mrweasel on 1/3/22, 11:17 AM

    I've struggled with this exact problem. A client developed a SPA and an API to go with it. Because "reasons" they wanted the API to live on api.customersite.com. Fair enough, that's their problem. Except it's not, because the developers have no idea how CORS work, only that it's a thing. So their API can't send CORS headers back, they never implemented that and apparently can't figure out how to make it work.

    Instead, we now have a reverse proxy (haproxy) that "fixes" the missing CORS headers, by intercepting the OPTIONS call and return a dummy response with the correct headers included. The developer basically understand NOTHING in regards to CORS, so whenever the silly SPA breaks, the logic is always the same: "CORS is broken, fix it". At not point has it been an option to fix the API service to include the correct headers.

    We could just have moved the API to /api and saved days of debugging and writing work-arounds, but no, api.customersite.com looks more professional.

  • by weitzj on 1/3/22, 7:53 AM

    From my experience, when you want to have 12 factor apps and just build your Frontend/backend once, and don’t want to leak absolute urls in the Frontend code, you are better of with relative URL’s anyways, i.e. /api for your api, and let a reverse proxy do the proper mapping to your services.

    Then of course you don’t need CORS

  • by malft on 1/3/22, 6:37 AM

    Before you start rearchitecting your app, be sure to check if the only reason you're seeing preflights on GET is that someone added a dumb X-Requested-By header.

    (You can even do POSTs without preflight if you use a whitelisted content-type.)

  • by willseth on 1/3/22, 4:03 PM

    This will result in fewer requests, but the author didn't provide any test results demonstrating that the server load was appreciably reduced. Since the preflight OPTIONS request will be cached, it will only occur for some fraction of the total volume of requests. Since those requests are also very low impact, I'm skeptical that this pattern will have a consequential impact for most APIs.
  • by eins1234 on 1/3/22, 6:18 AM

    The author mentions Cloudflare as having the ability to rewrite requests from `api.website.com/*` to `website.com/api/*`. Is that functionality available as a page rule or would we have to use workers and pay for every request rewritten?

    I remember taking a look at this a while ago and only found ways to do this through workers, so was turned off by the potential cost scaling. Ended up just setting a high `Access-Control-Max-Age` and calling it a day. But maybe I've missed a more cost effective way to accomplish this?

  • by kzemek on 1/3/22, 8:27 AM

    Or you can have the browser cache the CORS header for up to 2 hours (cross-browser), for the same performance effect - but it will also work for third party website consumers of your API that you can't just put on your domain.
  • by ManuelKiessling on 1/3/22, 8:07 AM

    Here is an extensive step-by-step tutorial which describes in detail how to create and deploy a React-based web app frontend using TypeScript and Redux Toolkit on top of a Node.js based AWS Lambda backend with a DynamoDB database, connect and integrate them through API Gateway and CloudFront, and explains how to codify and automate the required cloud infrastructure and deployment process using Terraform.

    The resulting architecture does not require any CORS requests, too:

    https://manuel.kiessling.net/2021/05/02/tutorial-react-singl...

  • by vfistri2 on 1/3/22, 1:27 PM

    imho CORS is poorly designed mechanism, you should be able to flag api.domain.com as a safe place, making a single cors request at start of user interaction with your app. Also some sort of caching should be more than welcome.

    On a side note at previous company I've worked at, speed was critical, so we were forced to do same tactic, use /api instead of api.domain.com which resulted in huge improvements :)

  • by colinclerk on 1/3/22, 7:02 AM

    My favorite way to avoid preflights is to have Next.js handle the reverse proxy. It fits my mental model better to have it code-side instead of putting Cloudflare/Fastly in front.
  • by noduerme on 1/3/22, 11:20 AM

    Does this work if you want to allow an iframe to execute code in a parent browser window from a different domain? (Assuming you control both domains and can inject a different Sec-fetch-mode header into the parent page)?

    Context here: I'm maintaining a PWA that has to run on local iphones/android devices and maintain contact with a server on local networks in the 127.x block. But the point of download for the app itself is under an https domain on the open internet. It uses an iframe to check lots of local 127.x.x addresses until it finds one with a local server, and bootstraps itself to the code on the local server that way; unfortunately, it can't run as a true PWA because the iframe at the center of it violates CORS (due to the ban on mixing clear and SSL requests in the more recent versions of Chrome and Safari). Would be nice if the local servers could simply serve up their content and control the window without a whole domain-specific postMessage protocol.

  • by adamddev1 on 1/3/22, 9:42 AM

    Is avoiding the OPTIONS request as simple as keeping a fetch to the same origin? I thought there were all kinds of rules for something to be considered a "simple request", like it can't be JSON, can't be PUT or DELETE, etc. I haven't looked much into this but does someone have a clear answer?
  • by SergeAx on 1/3/22, 10:13 AM

    Hint for those who didn't fall for cloud behemoths vendor lock. Deploying your app to plain VPS you still need a reverse proxy like nginx to handle raw requests and terminate TLS. So it is natural to let it do the routing part too.
  • by nesarkvechnep on 1/3/22, 7:35 AM

    Aren’t `OPTION` requests useful when you want to build an actual REST API and not what most devs call everything having an HTTP interface? The clients can use the requests to understand which actions they can apply to the resources.
  • by three14 on 1/3/22, 3:06 PM

    I was interested to note that the Dropbox API offers a hack to avoid the extra CORS request - among other details, their server accepts this:

    - Set the Content-Type to "text/plain; charset=dropbox-cors-hack" instead of "application/json" or "application/octet-stream". [0]

    ...which is allowed in a "Simple" request that doesn't require a separate round trip for OPTIONS.

    [0] - https://www.dropbox.com/developers/documentation/http/docume...

  • by donatj on 1/3/22, 7:42 AM

    Is there any way to read the OPTIONS response from JavaScript?

    I'm guessing not, but if there was, theoretically could you just include your API response in the CORS rejection response?

  • by zemnmez on 1/3/22, 9:14 AM

    Please use Access-Control-Max-Age instead. CORS has some nice security properties.
  • by ngrilly on 1/3/22, 11:33 AM

    I don’t understand what the article is actually recommending. To avoid CORS requests, I usually proxy www.foobar.app/api to api.foobar.app, but it seems like the article is suggesting the opposite.
  • by zagrebian on 1/3/22, 8:13 AM

    Looking at some of my HTTP requests, I noticed that

    * `Sec-Fetch-Site: cross-site` can be `Sec-Fetch-Mode: no-cors`

    * `Sec-Fetch-Site: same-origin` can be `Sec-Fetch-Mode: cors`

    It looks like all four combinations of these two headers are possible.

  • by dinkleberg on 1/3/22, 1:07 PM

    This is quite timely, was getting frustrated with some CORS issues last night because I am using the domain & api.domain schema. Will have to give this a try.
  • by r_singh on 1/3/22, 5:59 AM

    Try doing this with Heroku and you’ll decide that you want CORS after a few hours.
  • by crazypython on 1/3/22, 10:47 AM

    Going to implement this to improve Denigma.app load time!
  • by wdb on 1/3/22, 8:02 AM

    Didn't we do this back in the day for IE?
  • by cwilby on 1/3/22, 8:50 AM

    I'm not 100% on this, but doesn't disabling CORS essentially re-introduce CSRF?
  • by avereveard on 1/3/22, 7:40 AM

    Or just declare JSON as text, simple cors supports authentication, no need to mess with routes.
  • by geuis on 1/3/22, 6:43 AM

    > You don’t need that CORS request

    …if you’re using a proxy like Cloudflare.

    The short version of this post is that if your api is hidden behind Cloudflare, you can have it proxy requests to you api that lives on a subdomain.