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.
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.
> 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.
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.
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.
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. ;-)
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.
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.
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'.
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.
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.
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 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.
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).
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.
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.)
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 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.
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.
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.
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?
75 comments
[ 2.9 ms ] story [ 109 ms ] threadThey 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.
> 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.
The implicit assumption being that internally everything trusts the proxy?
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.
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.
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.
But yeah, that's kind of the point of running a cluster. One thing to look after. What would you rather be running?
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...
- 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.
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).
- 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.
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.
CORS is needed for GET if the request needs to send headers like `Authorization`, AFAIK
https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_...
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.)
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).
> 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.
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.
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.
If the latter, where are you getting 15-10 second figures from?
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.
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.
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/
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.
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.
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.
I'm wondering whether the http://hn.algolia.com/opensearch.xml configuration could help here, might need to tweak it a bit.
(Algolia charges thousands of dollars for some of their services and I find it the worst search system currently used on popular sites.)