12 ms·
Cache your CORS
- ryenxx3 4y ago[flagged]
- coding123 4y agoDamn, I am kinda surprised we don't have this enabled on our site. Thanks for dropping this tonight.
- mh- 4y agoA few years ago (last I looked), most big ecommerce sites missed this. Even ones that had invested quite a lot in the performance of their site.
- ShinTakuya 4y agoVery good and important advice, would advise caution when implementing this though - consider a shorter period when you make changes to CORS headers so that if you make a mistake you don't accidentally cut off your frontend for 24 hrs. I guess you could hack your way around it in an emergency but it'd still be painful.
- djbusby 4y agoThe article mentions that Chrome max value is 7200 seconds (2h) but FF is 24h. In practice for our app we rolled out with 600 to make sure things were working. And now we run at 28800 (6h) (but 2h on Chrome)
- ShinTakuya 4y agoOh yeah I did observe that but couldn't bother making the distinction. In my head, that's the worst case for a decent portion of users (2-5% depending on where your users predominantly live and other demographics) so I consider that to be a 24 hr outage. Your approach definitely sounds sane, would encourage others to do the same rather than following the article from the get go.
- WirelessGigabit 4y agoJust like DNS!
- ShinTakuya 4y agoOh yeah, DNS can be very painful. Definitely been burnt in the past. I generally lower the TTL to 5 minutes a day or so ahead of making any changes just to reduce risk, but DNS is even worse given that not everyone even respects TTL.
- silisili 4y agoI used to work for a very large DNS service that charged by the query count. The metrics said reducing DNS ttl from 24h to 10 mins only increased the number of requests by some small percentage (my memory is failing, I want to say 10%) due to a hundred external factors, including companies not respecting TTLs. We usually recommended they kept it below 5 mins, and could show query counts wouldn't scale linearly.
- dspillett 4y agoI've taken to keeping my TTLs at 5 minutes as a default for personal stuff. The potential extra latency of a full update every time you access a not-very-often accessed resource is fine and even for commonly accessed things the performance difference is negligible. Though I'm away I'm putting a little extra load on DNS caches elsewhere as they need to make extra recursive queries, so I might not do that for a high traffic service. > not everyone even respects TTL This used to be a problem with at least one common DNS cache, where it would see a very small value as an error and apply its own default (24 hours IIRC) instead. 10 minutes was fine, but 9m59s and it would not update until next day (the threshold may not have been 10 mins, it could have been 500s (8m20s), but it was something of that order). I'm pretty sure that is not longer a common DNS daemon, or if it is that behaviour has been fixed, so these days I'm not really concerned for my projects. For work things I might be a bit more restrained with short TTLs, just in case (for personal projects I can take the “it is not my fault your DNS setup is broken” line, but that sort of attitude doesn't always fly in a commercial environment!).
- 4y ago
- harry1989 4y agoExcellent suggestion. These days chrome hides the preflight requests by default and you miss to notice the latency added for each of those CORS calls. Also we don't deal with CORS unless it's an external plugin that we include in our site. Earlier we had subdomains like api..com, static..com to parallelize network requests which required CORS to be setup. With H2 we got rid of all of them and load everything from a single domain. This reduced the CORS surface area.
- corentin88 4y agoWhat’s H2?
- jiggawatts 4y agoHTTP/2
- end_of_line 4y agoThere is a in memory database H2. Anyways, I have never used h2 as short form of http/2
- deleted 4y ago[deleted]
- harry1989 4y agoYes it is http2. H2 is technically http2 over SSL.
- bluepnume 4y agoI built fetch-robot (https://github.com/krakenjs/fetch-robot https://github.com/krakenjs/fetch-robot) to avoid dealing with CORS preflight requests. And the associated maze of request and response headers you need to use to negotiate in the preflight.
- emilsedgh 4y agoThis is actually a very clever hack. What are the gotchas?
- turblety 4y agoIt's 44kb. Although I'm sure it could be made much, much smaller.
- treve 4y agoThis is pretty great. We have a number of applications that have different API and frontend origins, and it's frustrating to see that every request needs another roundtrip. So I started thinking about how we did things pre-CORS and I've been wanting to build this frame-proxy in our API. Pretty helpful!
- cakoose 4y agoUnfortunately this caching is still per-path. For example: GET /v1/document/{document-id}/comments/{comment-id} For every new document-id or comment-id, there will be a new pre-flight request. Alternative hacks: Offer a variant of your API format that either 1. Moves the resource path to the request body (or to a header that is included in "Vary"). Though the rest of your stack (load balancing, observability, redaction) might not be ok with this, e.g. do your WAF rules support matching on the request body? You also will no longer get automatic path-based caching for GET requests. 2. Conforms to the rules of a CORS "simple" request [1], which won't trigger a pre-flight request. This is what we did on the Dropbox API [2]. You'll need to move the auth information from the Authorization header to a query parameter or the body, which can be dangerous wrt redaction, e.g. many tools automatically redact the Authorization header but not query parameters. [1] https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simpl... [2] https://www.dropbox.com/developers/documentation/http/documentation https://www.dropbox.com/developers/documentation/http/docume... (see "Browser-based JavaScript and CORS pre-flight requests")
- enlyth 4y ago> Offer a variant of your API format that either: 1. Moves the resource path to the request body GraphQL ducks
- danwee 4y ago> GraphQL And now you have two problems
- pas 4y agoBut you have a nice schema for your problem :) GQL is a bit ugly, but works well, kind of standardized, etc. Is there something similar for providing a batch endpoint for OpenAPI requests?
- marcofatica 4y agoGET /blog/1?with=comments,author&only=title,body,created_at,comments.body,author.name
- s-xyz 4y agoSorry I am not an expert, but just wanted to raise that I recall that in our firm we had to disable CORS caching a while back, as this was non compliant with some security standards. I think it had to do with this: https://vulncat.fortify.com/en/detail?id=desc.dynamic.html.html5_cors_prolonged_caching_of_preflight_response https://vulncat.fortify.com/en/detail?id=desc.dynamic.html.h...
- woeh 4y agoThe article you mention targets prolonged caching, which it defines as more than 30 minutes. Disabling might not have been necessary?
- AtNightWeCode 4y agoWow, thank you, never heard about this before. I really hope this is not entirely correct but I will check our sites for sure. If options requests often bypass the edge servers some of the services we use are way better than expected.
- unwind 4y agoCORS = Cross-Origin Resource Sharing. There, that wasn't so hard, was it? I assume this is written for web developers who find this the most familiar acronym in the world, but ... still, it would not kill anybody to include the definition of the acronym, perhaps even with a friendly link [1] to make it Even More Accessible. I'll be off looking at the lawn mowing robot, now. [1]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
- swyx 4y agoas far as acronyms go this is a pretty common one that a hacker news crowd should know without spelling out. shrug
- ShinTakuya 4y agoIn the web development world CORS is as common an acronym as REST or TCP. I sympathise with the endless need to look up acronyms but in my view anyone involved enough with web to need the above advice would (or should) know the acronym. I do appreciate your point of view, but inclusiveness shouldn't come at the cost of brevity when those who would be included wouldn't benefit from it.
- Dangsuckslol 4y ago
- quickthrower2 4y ago> They can't even spell rest. Neither can you. It is REST.
- remram 4y agoIf the domain wasn't literally called "httptoolkit" it would be hard to know that this article relates to the "web development world" at all. Though I do imagine most people on HN are part of that world.
- stblack 4y ago
- kareemsabri 4y agoWe just use a path prefix and a reverse proxy to save the pre-flight request altogether.
- thr0wnawaytod4y 4y agoCould have other sec consequences, e.g. XSS in the API is now all of a sudden exploitable, just an example
- jeroenhd 4y agoThere are solutions for that; for example, it can also be avoided in prod by having a JS-specific subdomain that's the only domain whitelisted by the CSP, separate from the main API. HTTP/2 connection pools should be recycled and simple <script> inclusions don't require CORS so I don't expect many downsides. As an added bonus, such a configuration would be easier to use in combination with a CDN.
- lbriner 4y ago"just use"! What if the API is shared between multiple domains? Do you need to reverse proxy it everywhere? What if it is a public API for third-party sites?
- kareemsabri 4y agoYes, just use. The fact that less common edge cases exist doesn't undermine the solution. The example in the article was example.com and api.example.com, the most common setup imo. > What if the API is shared between multiple domains? Do you need to reverse proxy it everywhere? It would depend on your specific setup. We use a single nginx server entry to proxy many domains. > What if it is a public API for third-party sites? Then this approach would not be viable.
- CoolCold 4y agoI could be in some sort of bubble, but hardly can remember the case when dynamic part of request has not flown through some sort of reverse proxy or fully capable webserver (like Apache) anyways. On dev envs of course seen, but not in production.
- zemnmez 4y agoAccess-Control-Max-Age has, unfortunately a big security caveat which is that it is cached on a per-endpoint basis. Because Access-Control-Allow-Origin only allows one origin specification, if you previously used the Origin header to determine who could access the API, your next API requestor will effectively get your last response. For example, to allow abc.com AND bcd.com, you could check Origin and if correct return Access-Control-Allow-Origin: *. In this case, setting Access-Control-Max-Age will mean this applies to any site* after a single successful request. If you return the site's name, this will break CORS* for that amount of time if an attacker makes a single request to it.
- nmjenkins 4y agoThis is because you have forgotten to return a `Vary: Origin` header in the response. If you don’t do this, caches will presume the response is the same regardless of the Origin header in the request and so you will get the bug you describe.
- zemnmez 4y agoVary has no effect on the cors preflight cache: https://stackoverflow.com/questions/42848208/cors-preflight-response-includes-varyorigin-and-access-control-max-age https://stackoverflow.com/questions/42848208/cors-preflight-...
- kevincox 4y agoOf course note that some common CDNs just ignore the `Vary` header. cough Cloudflare cough. Also IIRC not refusing to cache anything with a `Vary` header but caching it anyways and serving it no matter what headers the client sends.
- langsoul-com 4y agoI hate cors, the biggest pain in the ass I have to constantly deal with.
- hugobauer 4y agoDoesn't moving the API from the subdomain to the /api path on the same subdomain as the website solve the problem?
- lbriner 4y agoYes but 1) You would need a proxy to redirect the /api path to another application and 2) The API could easily be hosted separately so you would send your request via the main origin server just for it to actually go somewhere else. 3) You are not necessarily talking about a private API for an app but a shared API for multiple domains in which case, this is not a solution.
- pantulis 4y agoA quick hack is to cache OPTIONS in Varnish.
- pimterry 4y agoHey, I'm the author of this post, thanks for sharing it @aloukissas! I've also built a tiny "just tell me what to do" SPA for debugging your CORS configurations that everybody reading this might find useful too: https://httptoolkit.tech/will-it-cors/ https://httptoolkit.tech/will-it-cors/
- colinclerk 4y ago> In practice, almost all cross-origin API requests will require these preflight requests, notably including At Clerk, we took the opposite approach, and restructured our API so it fits within the narrow window that does not require a preflight I wrote a little about it here: https://clerk.dev/blog/skip-cors-options-preflight https://clerk.dev/blog/skip-cors-options-preflight
- chrisweekly 4y agoI'm a big fan of "BFF" (Backend For Frontend), which among other benefits, sidesteps CORS altogether. See eg https://remix.run/docs/en/v1/guides/bff https://remix.run/docs/en/v1/guides/bff
- yonran 4y agoThis is a good practical article. One minor clarification: > cross-origin API requests will require these preflight requests, notably including: … Any request including credentials No, setting XMLHttpRequest’s withCredentials:true and fetch’s credentials:"include" to send the user’s Cookie with the request does not imply that a preflight request must be made, since <script> and <form> sent the Cookie with cross-site requests (back in the CSRF days when the default Cookie SameSite flag was effectively SameSite=None). Maybe he was referring to a custom header such as Authorization, which does trigger prefetch.