75 comments

[ 2.9 ms ] story [ 109 ms ] thread
My number one solution to any and all CORS issues is to never ever set up an api.* domain. Always do /api on the same domain. If the requests are for third parties, put a proxy on your own hostname if at all possible.
I think this a good interim "solution", but it won't work for everyone. On a larger scale it's rife with issues: e.g. browsers often have a limit to how many downloads they'll do from a single domain in parallel, so stuffing API, assets and pages all into the same domain can limit performance. The other thing is that using DNS to load balance between servers is much more lightweight than using an HTTP proxy / load balancer to direct traffic.
HTTP/2 virtually eliminates the limit on concurrent requests. For example Firefox allows 100 concurrent requests by default with H2.
Nobody has an excuse not to use either HTTP/2 or QUIC on a modern website.

They should also use just one domain - DNS lookups are a pretty slow and flaky part of the process of loading a webpage, and downloading loads of HTTPS certificates for all the domains involved isn't great either.

Managing just one domain is also a lot easier - less to monitor, less to maintain, simpler architecture, etc.

The only remaining reason for extra domains is to act as a security boundary - for example when you're running untrusted user javascript.

Your statements are overly definite.

> Nobody has an excuse not to use either HTTP/2 or QUIC on a modern website.

Sure they do. HTTP/2 is not uniformly superior to HTTP/1.1, primarily due to head-of-line blocking; so that on a poor quality network connection, with multiple HTTP/1.1 connections it’s quite possible that one will come through while another takes longer due to packet loss, while on HTTP/2 it’s all on one TCP connection, so that packet loss affects all requests. That’s what QUIC is about fixing. HTTP/2 is also currently incompatible with WebSockets (there’s a draft in progress for resolving that), so HTTP/2 offers no benefit for WebSocket domains or little-to-no benefit for WebSocket-heavy stuff. HTTP/2 also works differently from HTTP/1.1, so that certain moderately obscure combinations of types of long-lived requests can’t be cleanly terminated in HTTP/2 as they can be in HTTP/1.1 (sorry I’m being vague—I can’t recall the details off-hand). And there’s just the usual “existing software that doesn’t support HTTP/2” justification as well.

(Despite the listed criticisms of HTTP/2, I do agree that almost everyone should be using HTTP/2.)

As for QUIC (Google or IETF variant), it’s still experimental, with various unknowns that are being nutted out, and is not suitable for wide deployment by most entities just yet.

> The only remaining reason for extra domains is to act as a security boundary

No, that’s merely one of the best justifications for additional domains. There are other reasons for using other domains, though I would agree with a less strongly worded version of your initial declaration: that they should try, if convenient, to use just one domain.

This isn't a practical solution for embedded web servers where even six concurrent requests can be taxing.
Wouldn't it make sense to put the assets that don't need need API access on other domains?
(comment deleted)
Concurrent connections per domain is much less of an issue now. The max is high enough most apps will never hit it.
(comment deleted)
Yes, exactly. This is also very easy to implement since most webservers have a proxy like nginx. It is also very easy in Kubernetes via Ingress resource.
Putting a proxy on your own hostname solves the performance problem, but you need something that parses and re-sends requests, not just a general HTTP proxy - otherwise, the third party can e.g. serve malicious Javascript code to your users within your origin.
Are you able to elaborate further on what attack vector you're thinking of (if only so I can google it) - JSON hijacking maybe?

The implicit assumption being that internally everything trusts the proxy?

I believe they are referring to proxying requests to third parties, rather than the first part of the post which refers to proxying to your own backend.

Proxying requests to a third-party will mean browser security features like CORS will assume they are entirely safe due to them appearing to come from your domain, and not “gist.github.com” etc.

I'm not following this. In the given example you would configure a proxy to route /api requests to your API cluster. How would an attacker inject a different backend origin?
They wouldn't, but if you proxy arbitrary requests for UnicornGifs.sexy through your CatSharing.com backend, and UnicornGifs.sexy gets owned up (or even just mischievous), it can now generate malicious content in the same origin as CatSharing.com --- the browser can't differentiate which is which. And that forged CatSharing content will be valid in any page on CatSharing.com, not just the specific pages that include it.

The point is that when you serverside-proxy requests to third parties, you're implicitly trusting the third party to control the DOM for any app in your domain.

Oh right, I missed the last sentence where OP suggested proxying to third parties.
How is that different from the unproxied scenario? If unicorngifs.sexy gets pwned, your users get pwned, whether or not there’s a proxy in the middle.
(comment deleted)
(comment deleted)
If you load javascript from there - yes, but if you iframe to there - no. You can design a solution that uses cross-origin browser protection to protect your users from getting owned in case a partner is compromised.
I'm assuming this is a trusted endpoint. Either something you own or a vendor with a contract. If you're calling random endpoints from your pages, then that's another kettle of fish.
Yes, sorry for writing such a short message - I figured you were probably aware of these considerations, but I was pretty sure that not all your readers were. ;-)
My third party requests are to a CDN. I think adding a proxy to my own domain completely defeats the point of using a CDN.
What if you can’t because you want to use a hosted provider for your homepage like wix? Which doesn’t support reverse producing?
Why not have your frontend proxy running somewhere else, pointing both to Wix and to the evil ad network? Wix will still think it's the host, so it should be fine.
The you have to deal with CORS. But any site that has real complex interactions to design around isn't going to be hosted on Wix.
More web app madness. The main lesson I took from Michal Zalewski's excellent book "The Tangled Web" was I hope I never have to write a web application. There are far too many traps.
You're getting downvoted but after 20+ years of writing web applications I have to agree with you. This stuff is a mess.
I second this.

Not badmouthing an earlier poster but statements like... "It is also very easy in Kubernetes via Ingress resource." No... this is the opposite of 'very easy'.

Yes, 20+ years of this madness too.

I work with devs who think it’s “very easy with Kubernetes”. Three guesses who’s always fixing the Kubernetes cluster...
Ha! Honestly, I'm actually not sure if you're meaning it's you who's having to fix things, even though they set it up, or if you mean one of them is always fixing it.
I worried about my clarity too lol. It’s always Ops who’s fixing it (i.e. me).
What are the most common problems with it?
Did you set it up right? ;)

But yeah, that's kind of the point of running a cluster. One thing to look after. What would you rather be running?

Could you please stop creating new accounts for every few comments you post? We ban accounts that do this, and it's in the site guidelines: https://news.ycombinator.com/newsguidelines.html.

HN is a community. You don't have to use your real name, but users need to have some consistent identity for others to relate to. Otherwise we may as well have no usernames and no community at all. That would be a different kind of forum. Anonymity is fine, and throwaways for a specific purpose are ok, just not routinely.

https://hn.algolia.com/?sort=byDate&dateRange=all&type=comme...

Could you please suck my ass? :)
I think we are seeing big changes in the platform that actually make it a very exciting time to be a web developer.

- Much better tooling & testing

- Excellent editors like VSCode

- Javascript ES6-7 (and the renewed energy of the language)

- GPU accelerated CSS transitions and effects

- Web assembly

- Modern component frameworks & state management

- So many APIs to cover things you used to have to do yourself: payment, transactional email, authentication

- More expressive APIs like GraphQL

- No real need to maintain infrastructure: publish to CDN services; consume APIs.

Compared to development on [favorite-platform-here] it may not be as seamless, but that's an unfair comparison. You have to compare it other forms of cross-platform development, and there the web really shines. It certainly has its flaws and gaps, but I love what's currently happening in the space.

Websites are at least supposedly are sandboxed so they are not as much of a risk as running native binaries. But this is getting worse and worse though as browsers expose more and more of their host operating system's functionality. The benefits of using a website instead of a native app are quickly disappearing, while the drawbacks have only been somewhat mitigated. We're getting to the point where browsers are worthy of the decades old criticism Emacs has received. They have eventually become an OS with many fine features - simply lacking a good web browser.
The main advantage to a browser is auto updates. It enabled a number of saas apps that would otherwise be very costly to replicate and maintain.
The main benefit is not only sandbox. It's openness (intend to be), linking to resources world wide, cross-platform, auto update. Native app is great but also costly to SME.
So no preflight is needed if Content-Type is not set? Any downside to having the API server just assume json?
The preflight cache can be quite effective if you're using a single-endpoint RPC protocol like GraphQL.
I came here to say the same thing. With a single-endpoint model like GraphQL, this isn't a problem anymore
I noticed this the first time I opened the terminal on my app. One setting later and it's not a problem anymore.
Yes, this aspect of CORS is not nice.

I've used https://github.com/jpillora/xdomain to get around this and thought it was a thoroughly excellent workaround. There are other libraries that do this too (and according to the GH page, Google use the same approach for their APIs).

Thanks for posting the link, this is a neat idea!
Now add in JWT and CSRF tokens and the payload size per request adds up even more. It's just turtles all the way.
Why do you need JWT and CSRF at the same time? If you have the JWT you can use that as the CSRF token.
I don't think that you can. The CSRF token must either be in the headers or the meta tags. Storing it inside the JWT cookie would defeat the purpose (unless you are not using cookies at all in which case you don't have to).
JWT don’t have to be in a cookie. It may very well be a bearer token in the Authorization header as well. I would assume that is the most common use of a JWT, but that is just an assumption.
I think that web apps will increasingly start using WebSockets for their APIs instead of HTTP. HTTP was originally designed for serving static files and it's very good at that; it's also conveninent for file uploads. For exchanging data, however, the WebSockets protocol is slightly better in my opinion; maybe just better enough that developers might very slowly switch to it over time. There are several things which make WebSockets appealing:

- No CORS issues.

- Data can be pushed to clients in real-time (this goes well with reactive UIs).

- No need to send headers with every call/response.

- The server is aware of the client's presence (and can detect the exact moment when they go offline).

WebSockets add a few extra challenges related to recovering from lost connections and rate-limiting but a lot of libraries and frameworks have already solved these problems.

The main reason why HTTP might stick around for a very long time in the future is because HTTP REST APIs are extremely widespread and well understood.

The problem with WS is that is significantly more complex than HTTP when it comes to caching.

With WS you can’t leverage a CDN hosted solution and instead would need some complicated CDN that is aware of state.

I like HTTP because it just works, especially behind buggy corporate and mobile network firewalls.

You only cache GET responses, no? For those you don't need CORS anyway, and anything that's user specific should remain strictly between you and the user, right? (And for that WSS is perfect.)
> For those you don't need CORS anyway

CORS is needed for GET if the request needs to send headers like `Authorization`, AFAIK

Yes. There are a few safelisted headers (and relatedly, content-types) that do not trigger a pre-flight; any GET that uses something outside of them (such as 'authorization") gets preflighted.
> - Data can be pushed to clients in real-time (this goes well with reactive UIs).

You’ve been able to do that for many years with server-sent events.

> - The server is aware of the client's presence (and can detect the exact moment when they go offline).

You are overly optimistic in saying “exact moment”. You can probably detect when they probably go offline to within a minute, depending on various configuration factors, but they could then immediately come online again anyway.

This much being said: JMAP (https://jmap.io/), an RPC and object syncing protocol sponsored by FastMail and approaching finalisation in the IETF, has used regular HTTP and server-sent events, but one of us has also been prototyping and writing a draft for JMAP over WebSockets; see https://datatracker.ietf.org/doc/draft-murchison-jmap-websoc..., the introduction of which also lists two more key benefits of using a persistent connection for the transport: authentication only needs to be done once, and that request bodies can be compressed too. (In conjunction with the whole HTTP/2-and-WebSocket incompatibility matter, I initially informally opposed the effort as needless fragmentation, but when these benefits were pointed out and also that WebSockets will soon work over HTTP/2, I retracted my objections: there are clear and concrete benefits of WebSockets over plain HTTP/2—though they’re admittedly mostly not quite ready yet.)

In most applications, replacing HTTP with WebSockets is a big mistake.

HTTP is cachable, can be proxied, inspected via curl and examined in Chrome's devtools. More importantly, typical app communication with the server has request-response shape. If you're communicating over a WebSockets connection, that doesn't change the natural shape of your queries. So you have to correlate requests and responses and multiplex over the connection - essentially reimplementing HTTP, but poorly!

If you have the choice, request-response communication is preferrable because it's always going to be simpler than bidirectional communication. If server push is an important part of your user experience, implement it as an addition to your HTTP routes and as a completely separate pathway (and consider using SSE instead of WebSockets).

Making 1 request instead of 2 is definitely ideal, so following the recommendations here of using x/api instead of api.x is good advice, if it's low hanging fruit. However, otherwise, is it just me or doesn't this seem like a long tail optimisation?

> If you have a slow network, this could be a huge setback, especially when an OPTIONS request takes 2 seconds to respond, and another 2 for the data.

Firstly if your OPTIONS is taking 2 seconds, CORS is not your bottleneck. Even with a half-second PING due to lack of content distribution, and DNS resolution (shouldn't be an issue after the first request), an OPTIONS request should be of negligible size; the bulk of that 2 seconds isn't going to be network latency, that's a serious server-side perf. issue.

Secondly, similarly, if your OPTIONS is taking equally as long as your GET, CORS isn't your bottleneck.

Yes, considering CORS latency is worthwhile if having your API on the same domain is an easy change, but the title overstates the performance cost.

Once again, the quote is talking about slow network, not the server taking seconds to process an OPTIONS request.

Yes, two seconds and more can absolutely be network latency. If you don't think this is possible nor a regular occurrence for many people, you have been living in a bubble.

> two seconds and more can absolutely be network latency

Not at all contesting this.

> a regular occurrence for many people

Maybe I am living in a bubble, but this seems unlikely unless your definition of "many" is fairly liberal.

On a 2G connection you might get up to 1000ms latency, if you're in a remote part of the world could could add an extra 500ms or more due to geography. DNS lookup, TLS handshake, and the data transfer, you could potentially get up to 3+ seconds adding it all up. But—apart from aspects of that being one-off, as I mentioned above—generally speaking the whole lot is an example of an extreme. Your average user really shouldn't be seeing that.

This assumes good signal. If you have poor mobile signal then you can easily get 5-10 seconds latency.
Are you talking about page-load speeds here or just network latency (the former will obviously be much higher than the latter)?

If the latter, where are you getting 15-10 second figures from?

> this seems unlikely unless your definition of "many" is fairly liberal

In a world with more than 7 billion human beings and a large percentage of them being users of the internet, it doesn’t matter if we are talking about 1% or even only 0.01% of users. Any percentage will include many people.

It might only be many actual users if you’re Google or Facebook, though.

The title of the article is (HN mods have wisely edited it since my initial comment) "The Terrible Performance Cost...", which I'd say implies more impact than the reality.
I built a little library to help solve this problem: https://github.com/krakenjs/fetch-robot

All requests are channelled through an iframe on the domain you want to make requests to; then the iframe publishes a policy for which requests are allowed to go through.

CORS has a performance cost, and that is shitty. But CORS has many benefits.

Fine grain control over the options response, in certain contexts, is great. Being able to immediately ship 3rd party functionality and know that it will work is great. A unified API for your JS, your 3rd party JS, and all the API consumers out there, is also great. For me it's a security and speed-of-development thing; not a performance-while-travelling thing. This means it's great for full-featured apps used by businesses in Canada, the US, Japan, and most of Europe.

If a connection is seconds slow; and you have a project that needs to be a complex SPA, the right way of handling slow connections is to fallback to a simple server side pure HTML / CSS bare bones website that sets aggressive caches with crazy secure HTTP headers and uses encrypted cookies to handle session between a separate web server that communicates through the main backend API on the session's behalf. If you use Ember, this is trivial with Fastboot[0] if you've followed best practices, and not much work if you haven't.

Also, how you architect your API and frontend matters greatly. The reason I like following JSON API[1] is that EmberData-like data model mappers / persistence libraries mean that I can supply multitudes more information about the world in one response if I want to. Sure I have multiplexed HTTP2 set up, but if A needs B and B needs C1 through C20 and they each need their own D it's nice to be able to just send that all down and skip all the requests; and of course it's always possible to design the API to be optionally told to fetch only a subset and everything works easily for out of the box because these frontend libraries can rely on the standard.

The only thing I wish came with CORS was a small DSL, so I could do things like whitelist a pattern. But I get it; someone would probably screw up at some point and for what? It's kinda nice that a technology defaulted to secure for once.

[0] https://www.ember-fastboot.com/docs/user-guide

[1] I use v1.1 because I like to stay ahead wherever I can.

http://jsonapi.org/format/upcoming/

At Algolia, we use simple requests (https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Simpl...) in our JSON API. So that there's no OPTIONS requests being done in the most important scenarios (GET, POST).

This is a very neat trick because you can definitely ask an API for JSON via accept header (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Ac...) while being compliant with simple requests by using query strings and/or multipart/form for routes option.

If you work at Algolia, can you address (pass along) the issue that you can't use your search boxes with browser search keywords? (ie, right-click > "Add keyword for this search"/"Manage Search Engines")

At this point when I see the Algolia search box on a site, I know I won't get a full search experience it will be slightly crippled with a custom interface and a forced auto-complete with JS only interface support.

An example of this is vuejs.org vs developer.mozilla.org. With Mozilla dev site, I have 'mdn' as my search key (both Firefox and Chrome) and I can type "mdn grid-template-columns" and I get results. With vuejs.org and Algolia, I have to go through multiple clicks and visual hunt and peck to find what I am looking for. It's a productivity killer.

Apropos browser-search-keywords: If you work at youtube, please fix this: Searching via browser keyword should keep the search term in youtube's search-input-box. This changed a few months ago and it is so fscking annoying.

This is a good example why computer experience of today is so terrible:

"Never run a changing system" is dead. Systems are changing all the time.

You should submit a GitHub issue here: https://github.com/algolia/hn-search/issues

I'm wondering whether the http://hn.algolia.com/opensearch.xml configuration could help here, might need to tweak it a bit.

I save my criticism for systems I trully want to get better. And I actually regret posting my original comment here and hope the sites I use frequently just drop algolia entirely.

(Algolia charges thousands of dollars for some of their services and I find it the worst search system currently used on popular sites.)

I must be missing something! to avoid the cross domain issue, how about hitting the API endpoint from back-end function for example using CURL, and simply return the json response to the browser ?
I wonder what the impact of HTTP/2 on this is. Presumably connection multiplexing and keeping them open for longer works surely mitigate a lot of the latency?