79

I am building a REST application that makes use of CORS. Every REST call is different and I am finding that there is significant overhead in getting the preflight OPTIONS call. Is there a way to cache and apply a preflight OPTIONS result so that any subsequent calls to the same domain use the cached response?

MindWire
  • 3,739
  • 7
  • 30
  • 45
  • 1
    Appendum to question: If caching preflights for a smaller scope is not possible, what is the best way to limit the number of preflight requests in a RESTful application? – Rob W Aug 18 '12 at 20:37
  • 1
    A reverse proxy, check out nginx, will allow you to avoid the CORS pre-flight penalty. Simply map /api -> api.site.com – Douglas Ferguson Jan 05 '15 at 07:25
  • The [Wikipedia article on CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) has a nice flow chart that helped me to understand under what conditions a preflight call would be made (and thus how to avoid them). – Trenton May 22 '17 at 06:28

3 Answers3

107

Preflight can only be applied to the request, not to the entire domain. I brought the same question up on the mailing list, and there were security concerns. Here's the entire thread: http://lists.w3.org/Archives/Public/public-webapps/2012AprJun/0228.html

There are a few things to consider if you'd like to limit the number of preflight requests. First note that WebKit/Blink based browsers set a max preflight cache of 10 minutes:

https://github.com/WebKit/webkit/blob/master/Source/WebCore/loader/CrossOriginPreflightResultCache.cpp https://chromium.googlesource.com/chromium/blink/+/master/Source/core/loader/CrossOriginPreflightResultCache.cpp

(I'm not sure if this is true for other browsers). So while you should always set the Access-Control-Max-Age header, the max value is 10 minutes.

Next note that it is impossible to avoid a preflight on PUT/DELETE requests. So updates/deletes to your API will require at least one preflight every 10 minutes.

On GET/POST, avoid custom headers if at all possible, since these still trigger preflights. If your API returns JSON, note that a Content-Type of 'application/json' also triggers a preflight.

If you are willing to bend just how "RESTful" your API is, there are a few more things you can try. One is to use a Content-Type that doesn't need a preflight, like 'text/plain'. Custom headers always trigger preflights, so if you have any custom headers, you could move them into query parameters. At the extreme end, you could use a protocol like JSON-RPC, where all requests are made to a single endpoint.

In all honesty, because of the browser's preflight cache limit of 10 minutes, and REST's resource urls, the preflight cache is fairly useless. There's very little you can do to limit preflights over the course of a long running app. I'm hopeful the authors of the CORS spec will try to address this in the future.

monsur
  • 39,509
  • 15
  • 93
  • 91
  • I've tried moving some parameters from my url to my query string (for paging, I've added ?next=) but am still seeing a preflight to each request that has a different . Do you have any references you can point me to? – uglymunky Jul 10 '13 at 07:11
  • 3
    @uglymunky I will clarify that section. The preflight cache's key is based on an origin/url pair, where the url is the full request url. What I meant is that if you have a custom header, that will always trigger a preflight, even on a GET request. However, if you move that custom header into a query parameter, there will be no preflight for GET/POST requests. – monsur Jul 13 '13 at 03:35
  • 2
    @monsur Hi bit late to the party, but was wondering if you could advise.. I am currently passing a Authorization header (e.g. "Basic [base64 encoded string]") with all the requests to my API. The preflight requests are majorly hindering the app's performance. If I were to move to a token based auth system (with the token as a QS parameter in the URL), and get rid of custom headers entirely, would this be a way of avoiding the dreaded OPTIONS requests for GET and POST (I'm alright with there being some for PUT and DELETE)? – adaam May 22 '15 at 18:34
  • 5
    @adaam that should do the trick. I've just completed some testing in Firefox and Chrome to better understand the behavior, and can share the this: if your custom header is present, the browser will trigger an OPTIONS request unless the `Access-Control-Max-Age` is present and the exact same URL has been seen by the browser previously and it is within the period specified by the `Access-Control-Max-Age` and the browser in question doesn't shorten the value (Webkit, Firefox). If you get rid of the custom headers, and move them into the url, the browser won't send the OPTIONS request. Victory! – mikegradek Sep 21 '15 at 19:29
  • 2
    I love how they just stopped responding to your emails. All of this redundant calling, for a single edge case. – Rebs Oct 07 '15 at 03:36
  • 3
    In your answer, the Content-Type restriction is only relevant when *sending* data, not when receiving it, so there's no issue with servers returning arbitrary content-type headers. – letmaik Mar 24 '16 at 16:44
  • 1
    The fact that the cache lasts 10 minutes is outdated. From v76 Chromium allows for two hours. Firefox for 24h. Think that this should be updated in the answer so that everyone is aware: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age – Drubio Jun 21 '19 at 09:52
6

Try using xDomain

It was pretty simple for me to setup if using angular or jQuery. On your app server, add a proxy.html as stated in the help on the below link. Add some tags referring to the js files on your "client" and viola, no more pre-flights. This wraps in an iframe to avoid need for cors check.

https://github.com/jpillora/xdomain

Chewy
  • 624
  • 6
  • 19
1

One way is that you can point all your API calls to same domain as your front-end. Setup nginx on the front-end server to forward only API calls to API server. This will remove all pre-flight calls.

kishor borate
  • 120
  • 1
  • 10