479

I am building a web API. I found whenever I use Chrome to POST, GET to my API, there is always an OPTIONS request sent before the real request, which is quite annoying. Currently, I get the server to ignore any OPTIONS requests. Now my question is what's good to send an OPTIONS request to double the server's load? Is there any way to completely stop the browser from sending OPTIONS requests?

GoldenAge
  • 2,378
  • 4
  • 15
  • 46
Qian Chen
  • 12,930
  • 14
  • 54
  • 81
  • 4
    Quickest fix is definitely to just use Access-Control-Max-Age on CORS responses so it only gets requested once. – Ian Jul 18 '20 at 19:38

14 Answers14

418

edit 2018-09-13: added some precisions about this pre-flight request and how to avoid it at the end of this reponse.

OPTIONS requests are what we call pre-flight requests in Cross-origin resource sharing (CORS).

They are necessary when you're making requests across different origins in specific situations.

This pre-flight request is made by some browsers as a safety measure to ensure that the request being done is trusted by the server. Meaning the server understands that the method, origin and headers being sent on the request are safe to act upon.

Your server should not ignore but handle these requests whenever you're attempting to do cross origin requests.

A good resource can be found here http://enable-cors.org/

A way to handle these to get comfortable is to ensure that for any path with OPTIONS method the server sends a response with this header

Access-Control-Allow-Origin: *

This will tell the browser that the server is willing to answer requests from any origin.

For more information on how to add CORS support to your server see the following flowchart

http://www.html5rocks.com/static/images/cors_server_flowchart.png

CORS Flowchart


edit 2018-09-13

CORS OPTIONS request is triggered only in somes cases, as explained in MDN docs:

Some requests don’t trigger a CORS preflight. Those are called “simple requests” in this article, though the Fetch spec (which defines CORS) doesn’t use that term. A request that doesn’t trigger a CORS preflight—a so-called “simple request”—is one that meets all the following conditions:

The only allowed methods are:

  • GET
  • HEAD
  • POST

Apart from the headers set automatically by the user agent (for example, Connection, User-Agent, or any of the other headers with names defined in the Fetch spec as a “forbidden header name”), the only headers which are allowed to be manually set are those which the Fetch spec defines as being a “CORS-safelisted request-header”, which are:

  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type (but note the additional requirements below)
  • DPR
  • Downlink
  • Save-Data
  • Viewport-Width
  • Width

The only allowed values for the Content-Type header are:

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain

No event listeners are registered on any XMLHttpRequestUpload object used in the request; these are accessed using the XMLHttpRequest.upload property.

No ReadableStream object is used in the request.

Richard Downer
  • 772
  • 6
  • 15
Leo Correa
  • 16,594
  • 2
  • 42
  • 68
  • 1
    I have the following lines in my code which enables cross origin resources `w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))`, `w.Header().Set("Access-Control-Allow-Credentials", "true")`, `w.Header().Set("Access-Control-Allow-Methods", r.Header.Get("Access-Control-Request-Method"))`, and `w.Header().Set("Access-Control-Allow-Headers", r.Header.Get("Access-Control-Request-Headers"))`. I have my own safety check in my server, so I don't need the OPTIONS request. What I want to do is disable it from the browser. – Qian Chen Apr 30 '15 at 07:23
  • You may be able to get away with it by starting chrome with `--disable-web-security` but I'm not sure if that will disable preflight requests. – Leo Correa Apr 30 '15 at 13:40
  • 12
    But it's not realistic to set this Chrome flag to all general users. – Qian Chen May 11 '15 at 07:41
  • @ElgsQianChen true but again its a default behavior by the browser. I don't think there's a way to stop the preflight requests from the server end. – Leo Correa May 11 '15 at 14:46
  • 1
    It's not realistic to set the flag for all users, but you're the only user who has decided that they don't need this check. The rest of the world doesn't know that the cross-origin accessed server is happy with the cross-origin access until the server say so. Turning off their security-related settings is up to them. – Jon Hanna May 17 '15 at 17:34
  • 49
    It's incorrect to say preflight requests are required when making cross origin requests. Preflight requests are only required in specific situations, like if you are setting custom headers, or making requests other than get, head and post. – Robin Clowers May 27 '16 at 16:54
  • 4
    Funnily enough, when making a CORS request using jQuery, the JavaScript library specifically avoids setting the custom header, along with a word of warning to developers: _For cross-domain requests, seeing as conditions for a preflight are akin to a jigsaw puzzle, we simply never set it to be sure._ – Below the Radar Oct 17 '16 at 15:01
  • @ElgsQianChen — You haven't [accepted an answer](http://meta.stackoverflow.com/questions/5234/how-does-accepting-an-answer-work) for this question yet, but your comments indicate that this one solved your problem. Could you accept it? That will cause it to appear first on the page (before some of the incorrect and lower quality answers that appear first if people have their sort order set to "newest") – Quentin Nov 02 '16 at 06:56
  • 4
    How come if I do a `curl` to the api it works, but when running from chrome I get the error? – SuperUberDuper Sep 09 '17 at 10:02
  • 8
    @SuperUberDuper because CORS and preflight requests are a browser related matter. You can simulate CORS by adding an `Origin` header to your request to simulate as if the request was coming from a specific host (e.g yourwebsite.com). You can also simulate preflight requests by setting the HTTP method of a request to `OPTIONS` and the `Access-Control-*` Headers – Leo Correa Sep 09 '17 at 15:08
  • 2
    @LeoCorrea since your question is popular on SO and Google, I've updated the answer with some precisions about when a pre-flight request is not sent by the browser, 2018-way ;) – pomeh Sep 13 '18 at 15:27
  • The headers you added: `DPR, Downlink, Save-Data, Viewport-Width, Width` come from mozilla developers and are specific to Mozilla. Chromium may have other thoughts. At the end of the day, they are not documented, and are not listed in the current standards by [IETF](https://fetch.spec.whatwg.org/#concept-header) nor [W3C](https://www.w3.org/TR/cors/#access-control-allow-origin-response-header) so I would not include them in the list. – scavenger Jun 05 '19 at 17:38
  • 1
    Saying the "CORS OPTIONS request is triggered only in somes cases" is a gross understatement for web application use cases. Very seldom would you send a API request without either an `Authorization` header and/or use `application/json` as your content type, meaning OPTIONS requests are not sometimes required, they are practically always required. In data heavy web applications this means currently you *must* serve the API from the same host as the application to avoid the OPTIONS requests, which is a pity because there are many use cases where it would make sense to have different hosts. – André C. Andersen Jul 28 '19 at 14:13
269

Have gone through this issue, below is my conclusion to this issue and my solution.

According to the CORS strategy (highly recommend you read about it) You can't just force the browser to stop sending OPTIONS request if it thinks it needs to.

There are two ways you can work around it:

  1. Make sure your request is a "simple request"
  2. Set Access-Control-Max-Age for the OPTIONS request

Simple request

A simple cross-site request is one that meets all the following conditions:

The only allowed methods are:

  • GET
  • HEAD
  • POST

Apart from the headers set automatically by the user agent (e.g. Connection, User-Agent, etc.), the only headers which are allowed to be manually set are:

  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type

The only allowed values for the Content-Type header are:

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain

A simple request will not cause a pre-flight OPTIONS request.

Set a cache for the OPTIONS check

You can set a Access-Control-Max-Age for the OPTIONS request, so that it will not check the permission again until it is expired.

Access-Control-Max-Age gives the value in seconds for how long the response to the preflight request can be cached for without sending another preflight request.

Limitation Noted

  • For Chrome, the maximum seconds for Access-Control-Max-Age is 600 which is 10 minutes, according to chrome source code
  • Access-Control-Max-Age only works for one resource every time, for example, GET requests with same URL path but different queries will be treated as different resources. So the request to the second resource will still trigger a preflight request.
Neekey
  • 3,215
  • 1
  • 13
  • 9
  • 5
    Yes...this should be the accepted answer and most relevant to the quesiton..! – Rajesh Mbm Mar 30 '18 at 12:17
  • 8
    Thank you for mentioning `Access-Control-Max-Age`. That's the key here. It helps you avoid excessive preflight requests. – Idris Mokhtarzada Apr 26 '18 at 03:42
  • 1
    I am using axios to call get request. Where can I set Access-Control-Max-Age in axios request ? – Mohit Shah Sep 05 '18 at 10:04
  • +1 The Access-Control-Max-Age header is the key here. This should be the accepted answer! I setup 86400 seconds (24 hours) on the header and the prefligth request is gone! – revobtz Sep 15 '18 at 20:04
  • Is it correct to say that we should never use `application/json` because it is slower with OPTIONS? – Vitaly Zdanevich Oct 30 '18 at 14:56
  • 2
    @VitalyZdanevich no! Don't avoid `application/json` just because it makes your request non "simple" (and thus triggers CORS). The browser is doing its job. Set your server to return something a header like `Access-Control-Max-Age: 86400` and the browser will not resend an OPTIONS request for 24 hours. – colm.anseo Mar 10 '19 at 00:45
  • @colminator it depends on which browser you are using, Chromium will cap it to 10 minutes, please see https://cs.chromium.org/chromium/src/services/network/public/cpp/cors/preflight_result.cc?l=36&rcl=52002151773d8cd9ffc5f557cd7cc880fddcae3e – Bruno Rodrigues May 13 '19 at 05:19
  • If this spec is followed strictly, since only GET, HEAD, and POST requests are "simple", shouldn't the preflight OPTIONS request require its own preflight request (ad infinitum)? – scry Aug 14 '19 at 10:31
  • I think your analysis of the source is wrong. I think 10mins Max-Age is just the default. I think it does still accept the value if its provided by the server. – mikeysee Dec 17 '19 at 23:50
143

Please refer this answer on the actual need for pre-flighted OPTIONS request: CORS - What is the motivation behind introducing preflight requests?

To disable the OPTIONS request, below conditions must be satisfied for ajax request:

  1. Request does not set custom HTTP headers like 'application/xml' or 'application/json' etc
  2. The request method has to be one of GET, HEAD or POST. If POST, content type should be one of application/x-www-form-urlencoded, multipart/form-data, or text/plain

Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS

Community
  • 1
  • 1
device_exec
  • 1,596
  • 1
  • 7
  • 7
  • 14
    +1 for "custom HTTP headers"! In my case, they were causing the pre-flight request to be triggered. I refactored the request to send whatever I was sending in the headers as the request body and OPTIONS requests stopped being sent. – Andre Feb 04 '16 at 16:28
  • 27
    `application/xml` or `application/json` are not "Custom HTTP headers". The header itself would be `Content-Type` and calling that header "custom" would be misleading. – Leo Correa Jan 29 '17 at 15:40
  • 1
    Removed custom HTTP Headers and this worked like a charm! – Tim D May 11 '17 at 19:47
56

When you have the debug console open and the Disable Cache option turned on, preflight requests will always be sent (i.e. before each and every request). if you don't disable the cache, a pre-flight request will be sent only once (per server)

Nir
  • 955
  • 7
  • 8
  • 4
    oh what am I thinking. having debugging for hours this was my solution. cache disabled because of debugging console. – mauris Oct 22 '16 at 18:04
  • 1
    Even if the debug console is closed, preflight requests are sent – Luca Perico Jun 01 '18 at 16:09
  • 2
    Luca : that's true but the point is that the "disable cache" has no effect when the dev tools are closed. preflight requests are sent only once (per server of course) if cache is not disabled and are sent before each and every request if cache is disabled. – Nir Jun 03 '18 at 09:42
  • 1
    That was really helpful. – Anuraag Patil Oct 27 '19 at 18:15
36

Yes it's possible to avoid options request. Options request is a preflight request when you send (post) any data to another domain. It's a browser security issue. But we can use another technology: iframe transport layer. I strongly recommend you forget about any CORS configuration and use readymade solution and it will work anywhere.

Take a look here: https://github.com/jpillora/xdomain

And working example: http://jpillora.com/xdomain/

codeforester
  • 28,846
  • 11
  • 78
  • 104
XTRUST.ORG
  • 2,823
  • 4
  • 28
  • 53
  • is this actually a kind of a drop-in proxy? – matanster Jul 14 '16 at 12:43
  • 18
    "Options request is a preflight request when you send (post) any data to another domain." — That's not true. You can use XHR to send any POST request you could send with a normal HTML form without triggering a preflight request. It is only when you start doing things that a form can't do (like custom content types or extra request headers) that a preflight is sent. – Quentin Nov 02 '16 at 06:53
  • The solution here appears to rely on an iframe shim which works in some cases, but has some major limitations. What happens if you want to know the HTTP status code of the response or the value of another HTTP response header? – Stephen Crosby Mar 22 '17 at 21:25
  • 10
    iFrames were not made for that. – Romko Jan 30 '18 at 13:37
  • 1
    this is a software implementation and does answer the final question that was: "Is there any way to completely stop the browser from sending OPTIONS requests?". Long story short, there is no way to disable it in Mozilla or Chromium, it's implemented in the code and the only "working" options just circumvent the behavior. – scavenger Jun 05 '19 at 17:51
  • Simply POSTing to another domain is not the cause of prefilght request and in my opinion strongly recommending a solution involving iframes without explaining why is counterproductive – So You're A Waffle Man Mar 17 '21 at 10:22
19

For a developer who understands the reason it exists but needs to access an API that doesn't handle OPTIONS calls without auth, I need a temporary answer so I can develop locally until the API owner adds proper SPA CORS support or I get a proxy API up and running.

I found you can disable CORS in Safari and Chrome on a Mac.

Disable same origin policy in Chrome

Chrome: Quit Chrome, open an terminal and paste this command: open /Applications/Google\ Chrome.app --args --disable-web-security --user-data-dir

Safari: Disabling same-origin policy in Safari

If you want to disable the same-origin policy on Safari (I have 9.1.1), then you only need to enable the developer menu, and select "Disable Cross-Origin Restrictions" from the develop menu.

Community
  • 1
  • 1
Joseph Juhnke
  • 824
  • 9
  • 9
  • 5
    You should put more highlight on the part that says **"this should NEVER be a permament solution!!!!"**. Same origin policy is a very important browser security measure and should never be disabled when normally browsing the internet. – jannis Sep 07 '17 at 08:11
  • I wish the web would just work this way, so we can just request data we need from servers without extra hassle. – jemiloii Nov 14 '17 at 15:45
  • The '--disable-web-security' works, thank you. – pf_miles Mar 16 '21 at 06:40
15

As mentioned in previous posts already, OPTIONS requests are there for a reason. If you have an issue with large response times from your server (e.g. overseas connection) you can also have your browser cache the preflight requests.

Have your server reply with the Access-Control-Max-Age header and for requests that go to the same endpoint the preflight request will have been cached and not occur anymore.

enpenax
  • 1,278
  • 1
  • 12
  • 26
  • 1
    Thank you for this! The fact that `OPTIONS` requests will be cached with this header is pretty opaque in all the CORS documentation that I've read. – joshperry Jun 30 '17 at 21:25
  • And the cache only takes effect with the exact same url. I want a domain-level preflight cache which can really reduce the round trips. (CORS is silly!) – wonder May 04 '18 at 03:38
8

I have solved this problem like.

if($_SERVER['REQUEST_METHOD'] == 'OPTIONS' && ENV == 'devel') {
    header('Access-Control-Allow-Origin: *');
    header('Access-Control-Allow-Headers: X-Requested-With');
    header("HTTP/1.1 200 OK");
    die();
}

It is only for development. With this I am waiting 9ms and 500ms and not 8s and 500ms. I can do that because production JS app will be on the same machine as production so there will be no OPTIONS but development is my local.

MKroeders
  • 6,866
  • 2
  • 22
  • 37
AntiCZ
  • 1,440
  • 1
  • 9
  • 12
1

You can't but you could avoid CORS using JSONP.

Jose Mato
  • 2,505
  • 1
  • 13
  • 17
  • 5
    You only get an OPTIONS request if you are doing something [not simple](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Preflighted_requests). You can only make simple requests (GET, no custom headers, no authentication data) with JSONP, so JSONP can't substitute here. – Quentin May 16 '15 at 16:36
  • Yes, I know that but I don't know the exactly project requirements. I know it's no simple avoid cors but it depends of the project. In the worst scenario to avoid CORS you need to pass data using get parameters. So, JSONP could replace cors depending of the project requirements (as you said, using simple requests) – Jose Mato May 16 '15 at 16:39
  • I don't understand the design of the so called pre-flight. What could cause it to be unsafe, if the client decided to send data to the server? I don't see it makes any sense to double the load on the wire. – Qian Chen May 16 '15 at 16:55
  • @ElgsQianChen this can probably answer your question http://stackoverflow.com/questions/15381105/cors-what-is-the-motivation-behind-introducing-preflight-requests – Leo Correa May 17 '15 at 15:58
0

After spending a whole day and a half trying to work through a similar problem I found it had to do with IIS.

My Web API project was set up as follows:

// WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
    var cors = new EnableCorsAttribute("*", "*", "*");
    config.EnableCors(cors);
    //...
}

I did not have CORS specific config options in the web.config > system.webServer node like I have seen in so many posts

No CORS specific code in the global.asax or in the controller as a decorator

The problem was the app pool settings.

The managed pipeline mode was set to classic (changed it to integrated) and the Identity was set to Network Service (changed it to ApplicationPoolIdentity)

Changing those settings (and refreshing the app pool) fixed it for me.

David Schumann
  • 9,116
  • 6
  • 56
  • 78
Ju66ernaut
  • 2,282
  • 2
  • 20
  • 31
-2

What worked for me was to import "github.com/gorilla/handlers" and then use it this way:

router := mux.NewRouter()
router.HandleFunc("/config", getConfig).Methods("GET")
router.HandleFunc("/config/emcServer", createEmcServers).Methods("POST")

headersOk := handlers.AllowedHeaders([]string{"X-Requested-With", "Content-Type"})
originsOk := handlers.AllowedOrigins([]string{"*"})
methodsOk := handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "OPTIONS"})

log.Fatal(http.ListenAndServe(":" + webServicePort, handlers.CORS(originsOk, headersOk, methodsOk)(router)))

As soon as I executed an Ajax POST request and attaching JSON data to it, Chrome would always add the Content-Type header which was not in my previous AllowedHeaders config.

Kurt
  • 303
  • 4
  • 10
-2

One solution I have used in the past - lets say your site is on mydomain.com, and you need to make an ajax request to foreigndomain.com

Configure an IIS rewrite from your domain to the foreign domain - e.g.

<rewrite>
  <rules>
    <rule name="ForeignRewrite" stopProcessing="true">
        <match url="^api/v1/(.*)$" />
        <action type="Rewrite" url="https://foreigndomain.com/{R:1}" />
    </rule>
  </rules>
</rewrite>

on your mydomain.com site - you can then make a same origin request, and there's no need for any options request :)

David McEleney
  • 2,368
  • 20
  • 26
-2

It can be solved in case of use of a proxy that intercept the request and write the appropriate headers. In the particular case of Varnish these would be the rules:

if (req.http.host == "CUSTOM_URL" ) {
set resp.http.Access-Control-Allow-Origin = "*";
if (req.method == "OPTIONS") {
   set resp.http.Access-Control-Max-Age = "1728000";
   set resp.http.Access-Control-Allow-Methods = "GET, POST, PUT, DELETE, PATCH, OPTIONS";
   set resp.http.Access-Control-Allow-Headers = "Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,Keep-Alive,X-Requested-With,If-Modified-Since";
   set resp.http.Content-Length = "0";
   set resp.http.Content-Type = "text/plain charset=UTF-8";
   set resp.status = 204;
}

}

Rafa Cuestas
  • 335
  • 1
  • 2
  • 12
-7

There is maybe a solution (but i didnt test it) : you could use CSP (Content Security Policy) to enable your remote domain and browsers will maybe skip the CORS OPTIONS request verification.

I if find some time, I will test that and update this post !

CSP : https://developer.mozilla.org/fr/docs/Web/HTTP/Headers/Content-Security-Policy

CSP Specification : https://www.w3.org/TR/CSP/

Arnaud Tournier
  • 790
  • 8
  • 11