The use of dynamic requests to backend servers is prevalent in modern web applications. Despite its commonplace nature, there are some gotchas that trip up even experienced developers.
TL;DR
In order to successfully preflight a JSON-encoded request to a different domain, the browser's fetch request must include the HTTP header
access-control-request-headers: content-type
.In order to signal that a JSON-encoded request from a different domain is permitted, the remote server must be correctly configured to respond with the HTTP header
access-control-allow-headers: content-type
.
Basic CORS rules
Before getting started, let's review when Cross Origin Resource Sharing (CORS) is required.
- CORS is only used when a web page needs to access something that is hosted on a different domain.
- CORS is never used for HTTP requests made with the methods:
GET
,HEAD
orOPTIONS
- CORS is a browser-to-server protocol. It uses HTTP for communication, but HTTP per se knows nothing about CORS. Because of this, requests sent via
cURL
, or tools likePostman
, do not involve CORS, and will succeed, while the same request made by the browser will fail. - CORS does not enhance website security, rather it weakens security. Implementing it carefully is important.
Simple CORS requests
There are two types of CORS requests: simple and preflight.
Consider a scenario where a web page hosted on www.example.com
submits a <form>
to a backend server at api.example.com
. This is an example of a simple CORS request. It follows this path:
- The browser determines that the remote resource is not on the same domain as the web page, so it adds an extra header to the
POST
request:origin: www.example.com
. - The remote server recognizes that this is a request coming from a different host, and checks its rule book to determine how to respond.
- If the specified origin is not in the remote server's configuration, the server responds with status code
403
and an empty payload. - On the other hand, if the remote server is configured to honor requests from the specified origin, it handles the request, returning an appropriate status code such as
200
.
In this example, the type of payload is significant. POST requests having content type application/x-www-form-urlencoded
are made using the simple CORS protocol. This is the HTML <form>
submission standard. Here's what the software developer codes:
fetch('https://api.example.com/order_handler', {
method: 'POST',
mode: 'cors',
body: wwwFormData
});
One further note before moving on, the use of the CORS protocol is only triggered by the browser when dynamic requests are made by JavaScript. In the above example, if the wwwFormData
had been submitted directly by a SUBMIT button, CORS would not be invoked. The HTML for this type of submission is simply:
<form action='https://api.example.com/order_handler' method='POST'>
Preflight CORS
The second type of CORS request involves a preflight request made using the HTTP OPTIONS
method.
Consider the same scenario as above, but this time the developer chooses to format the data as JSON. The communication exchange follows this path:
- The payload is specified by the developer to be
content-type: application/json
. - The browser does not recognize this content type as one of the CORS "safelisted types", so it sends an
OPTIONS
request to the remote server with two headersorigin: www.example.com
andaccess-control-request-headers: content-type
. - As before, the remote server checks its configuration rule book to determine whether or not to honor requests from that origin, and further checks to see if it is ready to handle non-standard content types.
- If both of those conditions are satisfied, the server responds to the OPTIONS request with two headers:
access-control-allow-origin: www.example.com
andaccess-control-allow-headers: content-type
. - The browser sees that the remote server intends to honor the request, so it issues a POST request with the JSON payload and the
origin: www.example.com
andaccess-control-request-headers: content-type
headers. - The remote server honors the POST and responds with an appropriate status code.
The software developer codes it like this:
fetch('https://api.example.com/order_handler', {
method: 'POST',
mode: 'cors',
headers: {'content-type': 'application/json'},
body: jsonData
});
Configuring the remote server
Every web server has a way to enable CORS, and a way to allow content-type
headers. Here are links to the docs for a few of them:
- For Apache Web Server, add this to the remote server's configuration:
Header always set Access-Control-Allow-Headers "content-type"
- For Nginx Web Server, add this to the remote server's configuration:
add_header Access-Control-Allow-Headers "content-type"
- IIS Server CORS configuration
- Amazon EC2 CORS configuration
- Read Write Serve HTTP/2 Server CORS configuration
Originally published at Cross Domain JSON Requests. Read more JavaScript Fanboi articles here.
Top comments (0)