CORS stands for Cross-Origin Resource Sharing. When CORS is used to load a resource, the browser usually sends a “preflight” HTTP OPTIONS request. The server must respond specifying the origins it will interact with. It may also define additional constraints, such as the HTTP headers which can be sent.

The browser checks the current origin and the outgoing request against the server’s specifications. The request is allowed to continue if all the checks pass. Otherwise, the original request will be cancelled. You see a warning in the console when this happens.

When CORS Is Used

Browsers enforce CORS for Ajax and Fetch requests. The mechanism will also be used for web fonts, WebGL textures and canvas image draws with drawImage(). Any eligible request to a third-party origin will require a CORS exchange to take place.

CORS won’t be enforced if the request is seen as “simple”. A simple request must be GET, HEAD or POST with a content type of text/plain, application/x-www-form-urlencoded or multipart/form-data. The only permissible simple request headers are Accept, Accept-Language, Content-Language and Content-Type.

If the request doesn’t meet all of the above criteria, a CORS exchange will be commenced by modern browsers. It’s important to recognise CORS is a browser-based technology – you won’t ever encounter CORS while making requests manually, such as with curl in your terminal.

CORS exchanges don’t always send an OPTIONS preflight request. A preflight is used when the request would cause “side effects” on the server. This is generally the case for request methods other than GET.

Imagine a POST request to /api/users/create. The server would always create a new user but the browser may refuse access to the response if the request was subject to CORS. By sending an OPTIONS request first, the server has a chance to explicitly decline the real request. This ensures the user account isn’t actually created.

Handling CORS Client-Side

Although CORS is a browser technology, you can’t directly influence it with client-side code. This prevents malicious scripts from side-stepping CORS protections to load data from third-party domains.

CORS is usually transparent so you won’t be aware it’s in operation. If a CORS exchange does fail, your JavaScript code will see a generic network error. It’s not possible to get precise details of what went wrong, as this would be a security risk. The full details are logged to the console.

The only way of resolving a CORS failure is to make sure your server sends the correct response headers. Lets now look at how that is done.

Handling CORS Server-Side

You should firstly make sure your server handles OPTIONS requests correctly. You might need to create a new route in your web framework. You’ll generally need to accept OPTIONS requests to each endpoint that might receive a cross-origin request from a browser. The response doesn’t need to have a body but must include specific headers that inform the browser how to proceed.

Begin by adding the Access-Control-Allow-Origin header. This specifies the third-party origin which is allowed to communicate with your endpoint. Only one origin can be specified; you can handle multiple origins by dynamically setting the header’s value to the origin the request was sent from. You can get the current origin from the Origin request header.

Access-Control-Allow-Origin accepts * as a special wildcard value. This will allows CORS requests from all origins. Take care when using this – being specific with the allowed origins gives you more control and prevents malicious scripts from requesting data from your server.

Access-Control-Allow-Origin must be included in your server’s response to the real request, as well as the OPTIONS response. Once this single header is setup, a basic exchange with a third-party browser client will be permitted.

Specifying Cross-Origin Headers

CORS requests usually only support the “simple” request headers listed above. If you need to use any other header, such as Authorization or a custom header, your server will need to explicitly allow it in the preflight response.

Set the Access-Control-Allow-Headers header. Its value should be a comma-separated list of header names which will be accepted with the real request.

The browser would now permit a request with either the Authorization or X-Custom-Header headers to continue.

When the browser sends a CORS preflight request, it will send the Access-Control-Request-Headers header. This contains the list of headers that will be sent with the actual request. Your server code can use this information when determining how to respond to the preflight request.

Limiting to Specific Request Methods

Similarly to specifying request headers, server endpoints can define which HTTP methods should be allowed cross-origin. Set the Access-Control-Allow-Methods header as a comma-separated list of method names.

The browser sends the Access-Control-Request-Method header with CORS preflights. This lets your server know the HTTP method which will be used to make the final request.

Cookies and Credentials

CORS requests don’t normally send cookies as they could contain sensitive credentials identifying the sender. If you need to include cookies with a cross-origin request, you must explicitly enable this in your client-side code:

In addition, the server must set the Access-Control-Allow-Credentials: true response header to signal its agreement that credentialled cookies can be exchanged.

When using Access-Control-Allow-Credentials, you cannot use the wildcard (*) with Access-Control-Allow-Origin. The server must specify an explicit origin instead to safeguard user privacy. If the wildcard is sent, the browser will fail the request with a CORS error.

Preflight Caching

CORS OPTIONS preflights do add an overhead to each request you make. Although the delay should be barely perceptible on a good network connection, it’s nonetheless wasteful when you’re calling the same endpoint multiple times in quick succession.

You can tell the browser to cache preflight responses by setting the Access-Control-Max-Age header. The value should be the time in seconds that the browser is allowed to cache the response for. Subsequent requests to the same endpoint within the given period will not send a CORS preflight.

Conclusion

CORS can seem confusing the first time you encounter it. It’s a browser technology which is controlled by server responses. CORS is unavoidable and yet also uncontrollable, unless you have access to the server-side code you’re interacting with.

The actual implementation of CORS is quite straightforward. Make sure your API or CDN is sending the right response headers, most notably Access-Control-Allow-Origin. You’ll then have safe cross-origin communication that helps guard against bad actors.