Reputation:
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?
Upvotes: 625
Views: 755181
Reputation: 19839
OPTIONS
requests are what we call "preflight" requests in Cross-origin resource sharing (CORS).
They are necessary when you're making requests across different origins in specific situations.
This preflight 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 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.
Upvotes: 540
Reputation: 21226
TLDR:
GET
request... (might not work with POST
request)"mode": "no-cors"
instead of "mode": "cors"
(be aware of what this means)accept
and content-type
headers...OPTION
is appropriate!The accepted answers are all great.
I have a specific use case, when I use Chrome Dev Tools to Copy a previous GET
request from the "Network" tab. I right click > "Copy as fetch"
Then I can re-run the request using the console, pasting the Javascript fetch code and executing it... but that doesn't work! An additional OPTION
request is sent ("preflight request") -- my server does not know how to respond to the OPTION
verb, so the server responds with failure code "405" and so the main request is aborted/precluded as a "CORS Error".
The reason this is happening is the "Copy as fetch" will run the fetch
with the "cors"
value with the "mode"
parameter. More on the "mode"
parameter here and the "cors"
value here
fetch("...", {
...
"mode": "cors"
})
A OPTION
/Preflight request is the correct way to make a "cors"
request/comply with the "CORS Protocol", so the browser is behaving correctly.
If remove "mode": "cors"
then no OPTION
request/preflight request is sent, just a simple GET
request, which completes successfully.** You might also need to remove the "headers"
property from your fetch code too, and you might need to add back "mode": "no-cors"
.
It seems like removing/omitting This may not be true, so I explicitly use "mode"
parameter, (aka default behavior) is equivalent to using "mode": "no-cors"
... (can anyone confirm this?)"mode": "no-cors"
. More on "no-cors"
here, and an excerpt below. Note "no-cors"
has limitations! These limitations were fine for me, they did not affect my request/responses:
Prevents the method from being anything other than
HEAD
,GET
orPOST
, and the headers from being anything other thanCORS
-safelisted request headers... In addition, JavaScript may not access any properties of the resulting Response. This ensures that ServiceWorkers do not affect the semantics of the Web and prevents security and privacy issues arising from leaking data across domains.
Maybe removing the "headers"
property from the fetch call is required to satisfy the the "only CORS
-safelisted request headers" requirement.
As this other answer points out, using "no-cors"
may not be a solution, but a (bad) hack causing worse problems! Indeed , as mentioned in the description above, "JavaScript may not access any properties of the resulting Response" -- that defeats the purpose of the request!
But for me, I could access the properties of the Response in my JavaScript, using (await (await fetch(...)).json(). Maybe that's because my server response *does explicitly recognize my Origin* in the
Access-Control-Allow-Origin` response header...
Upvotes: 0
Reputation: 4294
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:
Access-Control-Max-Age
for the OPTIONS requestA simple cross-site request is one that meets all the following conditions:
The only allowed methods are:
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:
The only allowed values for the Content-Type header are:
A simple request will not cause a pre-flight OPTIONS request.
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.
Access-Control-Max-Age
is 600
which is 10 minutes, according to chrome source codeAccess-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.Upvotes: 366
Reputation: 1706
Please refer this answer on the actual need for pre-flighted OPTIONS request: What is the motivation behind the introduction of preflight CORS requests?
To disable the OPTIONS request, below conditions must be satisfied for ajax request:
application/x-www-form-urlencoded
, multipart/form-data
, or text/plain
Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
Upvotes: 154
Reputation: 656
you can also use a API Manager (like Open Sources Gravitee.io) to prevent CORS issues between frontend app and backend services by manipulating headers in preflight.
Header used in response to a preflight request to indicate which HTTP headers can be used when making the actual request :
and specify the "allow-origin" = localhost:4200 for example
Upvotes: 1
Reputation: 4275
OPTIONS request is a feature of web browsers, so it's not easy to disable it. But I found a way to redirect it away with proxy. It's useful in case that the service endpoint just cannot handle CORS/OPTIONS yet, maybe still under development, or mal-configured.
Steps:
Basically this approach is to cheat browser that OPTIONS request works. Considering CORS is not to enhance security, but to relax the same-origin policy, I hope this trick could work for a while. :)
Upvotes: 0
Reputation: 3402
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/
Upvotes: 39
Reputation: 2691
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.
Upvotes: 0
Reputation: 1323
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)
Upvotes: 70
Reputation: 443
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;
}
}
Upvotes: -1
Reputation: 3813
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 :)
Upvotes: -2
Reputation: 1503
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.
Upvotes: 11
Reputation: 383
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.
Upvotes: -2
Reputation: 882
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.
Upvotes: 22
Reputation: 1562
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.
Upvotes: 17