- Audience: developers
- Target: explain a web concept
- Preliminary knowledge: HTTP
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).
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:
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 ;)
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 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
To be considered simple, a request has to comply to each of these three rules (source: MDN):
- HTTP method must be either
GET
,POST
orHEAD
- it must contain only standard headers (full list here)
- in case the
Content-Type
(standard) header is present, its value must be one ofapplication/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 theOrigin
header's value is a match, return it as value ofAccess-Control-Allow-Origin
.
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:
Access-Control-Allow-Methods
: methods allowed when requesting this resource.Access-Control-Allow-Headers
: you know the drill now.
Let's have two examples here.
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:
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)
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.
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
andDELETE
methods will always trigger preflight requests- Webkit and Blink based browsers have a hardcoded maximum value of 10 minutes
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.
CORS is intended to allow domain owners to restrict access to their domain's resources from other domains.
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.
Your application server or HTTP proxy will allow you to specify it from a global namespace to a specific resource.
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.
https://www.w3.org/TR/cors/ https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS