Skip to content

Instantly share code, notes, and snippets.

@amessinger
Created January 29, 2019 10:42
Show Gist options
  • Save amessinger/f3eed0f37a1f71840714a6e419f7d236 to your computer and use it in GitHub Desktop.
Save amessinger/f3eed0f37a1f71840714a6e419f7d236 to your computer and use it in GitHub Desktop.
CORS
  • Audience: developers
  • Target: explain a web concept
  • Preliminary knowledge: HTTP

CORS

Disclaimer

This article is one among thousands; definitely not the most in depth; it just tries to lay down how its author got to work with CORS and not against (aka wildcarding all the things).

What?

Cross Origin Resource Sharing (aka CORS) is an access control mechanism implemented in browsers.

It aims to empower a domain to define how a web page served by a different domain can interact with its resources.

Note that a domain is defined by the following pattern: protocol://domain:port. Therefore, http://api.service.com,http*s*://api.service.com, http://*www*.service.com and http://api.service.com:*8000* are all different domains.

The follwing sequence diagragram will setup the stage:

Requesting a resource from a different domain

It might not be obvious, but it is not up to the origin domain, serving the web page, to define how it can interact with other domains.

The CORS policy can control up to 3 parameters coming from an incoming request:

  • Origin header: the domain that served the web page
  • HTTP method: the action the browser is trying to achieve
  • other headers: meta data that comes with the request (referred as terms in the article)

Resources subject to this policy are (source: MDN):

  • XMLHttpRequest & Fetch APIs calls
  • Web Fonts
  • WebGL textures
  • Images/video frames drawn to a canvas using drawImage
  • Stylesheets
  • Scripts

While you can define a global strategy on your domain, it will be resource (URL) specific from the browser's perspective. Same as caching ;)

How?

The idea is to let a web page know how it can interact with a given resource of a domain.

The domain can only inform a browser of its CORS policy when it's actually asked something, so the browser has to initiate dialog.

Simple requests

Simple requests will be sent right away by the browser. The Origin header will be set automatically.

Browser: Would you {{HTTP method}} this resource for me? Server: Sure, here is the response

Making a simple request

To be considered simple, a request has to comply to each of these three rules (source: MDN):

  • HTTP method must be either GET, POST or HEAD
  • it must contain only standard headers (full list here)
  • in case the Content-Type (standard) header is present, its value must be one of application/x-www-form-urlencoded, multipart/form-data, text/plain

This means that domains will control simple requests based solely on their origin domain, specified by the Origin header.

The domain will send back an answer with, in addition to the payload, the following header:

  • Access-Control-Allow-Origin: the domain the web page can originate from. It is worth noting that currently only single value and wildcard (*) are eligible options for this header. This can be easily circumvented by some smart routing on your proxy or application: you can specify a list of whitelisted domains (including regex) and if the Origin header's value is a match, return it as value of Access-Control-Allow-Origin.

Preflighted Requests

For other requests, the browser will send a preflight request, asking the domain what is the policy for the given resource.

Browser: What is the policy regarding this resource in those {{HTTP headers}}-defined terms?

Domain: Here is my CORS policy (for this resource)

Browser: Would you {{HTTP method}} this resource for me in those {{HTTP headers}}-defined terms?

While simple requests got controlled solely on their origin (Access-Control-Allow-Origin), preflighted requests are additionally controlled on their HTTP method and their headers. This information is therefore conveyed by the following preflight request response's headers:

Let's have two examples here.

Not So Simple Requests

Firstly let's add some authentication to our previous GET request through an Authorization header. Since this is a non-standard header, the browser is going to preflight it:

Making a simple request preflighted

Note that a preflight request is basically the same as the actual request, except that

  • its HTTP method is OPTIONS, hence has no payload
  • its response has no payload either (204 HTTP code)

Plain Preflighted Requests

Our second example will be a PUT request which is enough of a reason for the browser to preflight it. Let's keep our Authorization header and add a non standard Content-Type value for good measure.

Making a preflighted request

Caching Preflighted Requests

Preflight requests increase network activity but their number can be reduced by setting up the Access-Control-Max-Age header header, albeit with some limitations (source StackOverflow):

  • PUT and DELETE methods will always trigger preflight requests
  • Webkit and Blink based browsers have a hardcoded maximum value of 10 minutes

Making several preflighted requests

Why?

Let's say you've successfully setup your CORS policy on your domain and your shiny single page app (and others!) can now communicate with your API. All is fine.

Except that, at this point you might ask yourself why was this mechanism implement into every modern browsers.

Access Control rather than Security

CORS is intended to allow domain owners to restrict access to their domain's resources from other domains.

Preflighted Requests

Tips

Don't wildcard blindly

Wildcarding is easy but really will make your resources queryable by any web page served by external domains. Think twice before opting for this solution.

Adjust granularity when implementing the CORS policy

Your application server or HTTP proxy will allow you to specify it from a global namespace to a specific resource.

There is a counterpart to Access-Control-Allow-Headers

The domain can specify what non-standard headers of the response the browser can access by setting the Access-Control-Expose-Headers header. Yup, you might not be aware, but you can't read those by default.

Literature

https://www.w3.org/TR/cors/ https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment