Cross Domain JSON Requests

Correctly configuring CORS for content-type: application/json

by Joe Honton

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.


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 or OPTIONS.
  • 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 like Postman, 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 submits a <form> to a backend server at This is an example of a simple CORS request. It follows this path:

  1. 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:
  2. The remote server recognizes that this is a request coming from a different host, and checks its rule book to determine how to respond.
  3. If the specified origin is not in the remote server's configuration, the server responds with status code 403 and an empty payload.
  4. 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('', {
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='' 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:

  1. The payload is specified by the developer to be content-type: application/json.
  2. 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 headers origin: and access-control-request-headers: content-type.
  3. 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.
  4. If both of those conditions are satisfied, the server responds to the OPTIONS request with two headers: access-control-allow-origin: and access-control-allow-headers: content-type.
  5. 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: and access-control-request-headers: content-type headers.
  6. The remote server honors the POST and responds with an appropriate status code.

The software developer codes it like this:

fetch('', {
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

Cross Domain JSON Requests