Unlike simple requests, for "preflighted" requests the browser first sends an HTTP request using the
OPTIONS method to the resource on the other origin, in order to determine if the actual request is safe to send. Such cross-origin requests are preflighted since they may have implications for user data.
The following is an example of a request that will be preflighted:
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = handler;
The example above creates an XML body to send with the
POST request. Also, a non-standard HTTP
X-PINGOTHER request header is set. Such headers are not part of HTTP/1.1, but are generally useful to web applications. Since the request uses a
text/xml, and since a custom header is set, this request is preflighted.
Note: As described below, the actual
POST request does not include the
Access-Control-Request-* headers; they are needed only for the
Let's look at the full exchange between client and server. The first exchange is the preflight request/response:
OPTIONS /doc HTTP/1.1
HTTP/1.1 204 No Content
Lines 1 - 10 above represent the preflight request with the
Access-Control-Request-Method header notifies the server as part of a preflight request that when the actual request is sent, it will do so with a
POST request method. The
Access-Control-Request-Headers header notifies the server that when the actual request is sent, it will do so with
Content-Type custom headers. Now the server has an opportunity to determine whether it can accept a request under these conditions.
Lines 12 - 21 above are the response that the server returns, which indicate that the request method (
POST) and request headers (
X-PINGOTHER) are acceptable. Let's have a closer look at lines 15-18:
The server responds with
Access-Control-Allow-Origin: https://foo.example, restricting access to the requesting origin domain only. It also responds with
Access-Control-Allow-Methods, which says that
GET are valid methods to query the resource in question (this header is similar to the
Allow response header, but used strictly within the context of access control).
The server also sends
Access-Control-Allow-Headers with a value of "
X-PINGOTHER, Content-Type", confirming that these are permitted headers to be used with the actual request. Like
Access-Control-Allow-Headers is a comma-separated list of acceptable headers.
Access-Control-Max-Age gives the value in seconds for how long the response to the preflight request can be cached without sending another preflight request. The default value is 5 seconds. In the present case, the max age is 86400 seconds (= 24 hours). Note that each browser has a maximum internal value that takes precedence when the
Access-Control-Max-Age exceeds it.
Once the preflight request is complete, the real request is sent:
POST /doc HTTP/1.1
HTTP/1.1 200 OK
[Some XML payload]
Preflighted requests and redirects
Not all browsers currently support following redirects after a preflighted request. If a redirect occurs after such a request, some browsers currently will report an error message such as the following:
The request was redirected to 'https://example.com/foo', which is disallowed for cross-origin requests that require preflight. Request requires preflight, which is disallowed to follow cross-origin redirects.
The CORS protocol originally required that behavior but was subsequently changed to no longer require it. However, not all browsers have implemented the change, and thus still exhibit the originally required behavior.
Until browsers catch up with the spec, you may be able to work around this limitation by doing one or both of the following:
- Change the server-side behavior to avoid the preflight and/or to avoid the redirect
- Change the request such that it is a simple request that doesn't cause a preflight
If that's not possible, then another way is to:
- Make a simple request (using
Response.url for the Fetch API, or
XMLHttpRequest.responseURL) to determine what URL the real preflighted request would end up at.
- Make another request (the real request) using the URL you obtained from
XMLHttpRequest.responseURL in the first step.
However, if the request is one that triggers a preflight due to the presence of the
Authorization header in the request, you won't be able to work around the limitation using the steps above. And you won't be able to work around it at all unless you have control over the server the request is being made to.