Cross-Origin Resource Sharing (CORS)

Share cross-origin resources safely

Mariko Kosaka

The browser's same-origin policy blocks reading a resource from a different origin. This mechanism stops malicious sites from reading other sites' data, but it also prevents legitimate uses.

Modern web apps often want to get resources from a different origin, for example, retrieving JSON data from a different domain or load images from another site into a <canvas> element. These can be public resources that should be available for anyone to read, but the same-origin policy blocks their use. Developers have historically used workarounds such as JSONP.

Cross-Origin Resource Sharing (CORS) fixes this issue in a standardized way. Enabling CORS lets the server tell the browser it can use an additional origin.

request and response
Illustrated client request and server response.

A browser and a server can exchange data over the network using the Hypertext Transfer Protocol (HTTP). HTTP defines the communication rules between the requester and the responder, including what information is needed to get a resource.

The HTTP header negotiates the message exchange between the client and the server, and is used to determine access. Both the browser's request and the server's response message are divided into a header and a body.

Information about the message such as the type of message or the encoding of the message. A header can include a variety of information expressed as key-value pairs. The request header and response header contain different information.

Sample request header

Accept: text/html
Cookie: Version=1

This header is equivalent to saying "I want to receive HTML in response. Here is a cookie I have."

Sample response header

Content-Encoding: gzip
Cache-Control: no-store

This header is equivalent to saying "The data in this response is encoded with gzip. Don't cache this."

Body

The message itself. This can be plain text, an image binary, JSON, HTML, or many other formats.

How does CORS work?

The same-origin policy tells the browser to block cross-origin requests. When you need a public resource from a different origin, the resource-providing server tells the browser that the origin sending the request can access its resource. The browser remembers that and allows cross-origin resource sharing for that resource.

Step 1: client (browser) request

When the browser makes a cross-origin request, the browser adds an Origin header with the current origin (scheme, host, and port).

Step 2: server response

When a server sees this header, and wants to allow access, it adds an Access-Control-Allow-Origin header to the response specifying the requesting origin (or * to allow any origin.)

Step 3: browser receives response

When the browser sees this response with an appropriate Access-Control-Allow-Origin header, it shares the response data with the client site.

Share credentials with CORS

For privacy reasons, CORS is normally used for anonymous requests, in which the requester isn't identified. If you want to send cookies when using CORS, which can identify the sender, you need to add additional headers to the request and response.

Request

Add credentials: 'include' to the fetch options as in the following example. This includes the cookie with the request as follows:

fetch('https://example.com', {
  mode: 'cors',
  credentials: 'include'
})

Response

Access-Control-Allow-Origin must be set to a specific origin (no wildcard using *) and Access-Control-Allow-Credentials must be set to true.

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Credentials: true

Preflight requests for complex HTTP calls

When a web app makes a complex HTTP request, the browser adds a preflight request to the beginning of the request chain.

The CORS specification defines a complex request as follows:

  • A request that uses methods other than GET, POST, or HEAD.
  • A request that includes headers other than Accept, Accept-Language or Content-Language.
  • A request that has a Content-Type header other than application/x-www-form-urlencoded, multipart/form-data, or text/plain.

Browsers automatically create any necessary preflight requests and send them before the actual request message. The preflight request is an OPTIONS request like the following example:

OPTIONS /data HTTP/1.1
Origin: https://example.com
Access-Control-Request-Method: DELETE

On the server side, the app receiving the request responds to the preflight request with information about the methods the application accepts from this origin:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, DELETE, HEAD, OPTIONS

The server response can also include an Access-Control-Max-Age header to specify the duration in seconds to cache preflight results. This allows the client to send multiple complex requests without needing to repeat the preflight request.