14 ms·
The Performance Cost of CORS Requests on Single-Page Applications
- tootie 8y agoMy 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.
- makmanalp 8y agoI 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.
- panarky 8y agoHTTP/2 virtually eliminates the limit on concurrent requests. For example Firefox allows 100 concurrent requests by default with H2.
- londons_explore 8y agoNobody 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.
- chrismorgan 8y agoYour 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.
- kevin_thibedeau 8y agoThis isn't a practical solution for embedded web servers where even six concurrent requests can be taxing.
- bdcravens 8y agoWouldn't it make sense to put the assets that don't need need API access on other domains?
- deleted 8y ago[deleted]
- tootie 8y agoConcurrent connections per domain is much less of an issue now. The max is high enough most apps will never hit it.
- deleted 8y ago[deleted]
- izelnakri 8y agoYes, 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.
- JoachimSchipper 8y agoPutting 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.
- yomly 8y agoAre 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?
- graystevens 8y agoI 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.
- dasil003 8y agoI'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?
- tptacek 8y agoThey 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.
- gweinberg 8y agoMy third party requests are to a CDN. I think adding a proxy to my own domain completely defeats the point of using a CDN.
- tachang 8y agoWhat if you can’t because you want to use a hosted provider for your homepage like wix? Which doesn’t support reverse producing?
- jessaustin 8y agoWhy 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.
- tootie 8y agoThe 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.
- rwmj 8y agoMore 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.
- Zelphyr 8y agoYou're getting downvoted but after 20+ years of writing web applications I have to agree with you. This stuff is a mess.
- mgkimsal 8y agoI 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.
- upzone3 8y agoI work with devs who think it’s “very easy with Kubernetes”. Three guesses who’s always fixing the Kubernetes cluster...
- mgkimsal 8y agoHa! 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.
- jonny_eh 8y agoSo no preflight is needed if Content-Type is not set? Any downside to having the API server just assume json?
- tveita 8y agoThe preflight cache can be quite effective if you're using a single-endpoint RPC protocol like GraphQL.
- thangngoc89 8y agoI came here to say the same thing. With a single-endpoint model like GraphQL, this isn't a problem anymore
- infogulch 8y agoI noticed this the first time I opened the terminal on my app. One setting later and it's not a problem anymore.
- jarym 8y agoYes, this aspect of CORS is not nice. I've used https://github.com/jpillora/xdomain 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).
- Drdrdrq 8y agoThanks for posting the link, this is a neat idea!
- superasn 8y agoNow add in JWT and CSRF tokens and the payload size per request adds up even more. It's just turtles all the way.
- pas 8y agoWhy do you need JWT and CSRF at the same time? If you have the JWT you can use that as the CSRF token.
- superasn 8y agoI 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).
- bosyr 8y agoJWT 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.
- jondubois 8y agoI 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.
- callumjones 8y agoThe 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.
- pas 8y agoYou 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.)
- al2o3cr 8y ago> For those you don't need CORS anyway CORS is needed for GET if the request needs to send headers like `Authorization`, AFAIK
- lostcolony 8y ago
- lucideer 8y agoMaking 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.
- always_good 8y agoOnce 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.
- lucideer 8y ago> 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.
- nicoburns 8y ago
- bluepnume 8y agoI built a little library to help solve this problem: https://github.com/krakenjs/fetch-robot 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.
- 3pt14159 8y agoCORS 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 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/ http://jsonapi.org/format/upcoming/
- vvoyer 8y agoAt Algolia, we use simple requests (https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#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/Accept 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.
- RobertRoberts 8y agoIf 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.
- jackewiehose 8y agoApropos 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.
- redox_ 8y agoYou should submit a GitHub issue here: https://github.com/algolia/hn-search/issues https://github.com/algolia/hn-search/issues I'm wondering whether the http://hn.algolia.com/opensearch.xml http://hn.algolia.com/opensearch.xml configuration could help here, might need to tweak it a bit.
- nour_js 8y agoI 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 ?
- spacenick88 8y agoI 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?