CORS and kcgi
Kristaps Dzonsons
Background
Read the MDN article on CORS to get started, or jump directly to its definition in the fetch spec (the CORS spec is obsolete). In short, CORS (Cross-Origin Resource Sharing) is a way to specify how a user visiting one address (say, https://bsd.lv), is allowed to make a request of another page on a different domain (say, https://kristaps.bsd.lv).
Why is CORS important? Without it, a sneaky page might make requests of a different domain (say, your bank), masquerading as you. CORS is a technology for protecting users, not websites.
CORS is an evolving concept, so it's wise to make sure nothing has changed in the field since I've written this article. Or better yet, make a pull request with any changes.
Simple Pre-flight Handling
Let's start with OPTIONS
handling, documented lightly in
MDN
or described completely in the
RFC.
A CORS pre-flight
is an OPTIONS
HTTP request with the
Origin
request header
(MDN,
standard)
set. The browser sends this to determine what capabilities a client from
an origin host be permitted. The simplest reply to a CORS request is to
wildcard permission.
A wildcard response with Access-Control-Allow-Origin
header
(MDN,
standard)
instructs the browser that any request is allowed.
There are some limitations not covered in this document: for instance,
credentials are not allowed.
This uses the HTTP error code 204 (MDN, RFC) instead of code 200. There exist warnings suggesting that code 204 for this purpose is not handled properly by all browsers, but it's not exactly clear by which browser and in which circumstances.
Use of the Vary
header
(MDN,
RFC)
may not be relevant for simple cases, but as per the
specification,
it should be considered best practice.
Another common invocation that reduce some of the aforementioned limitations is to specifically assign possible responses to the origin, but not to investigate the origin in any way. This is similar to the Bad Old Days, when arbitrary requests were accepted from arbitrary origins.
This further leverages the Access-Control-Allow-Methods
header
(MDN,
standard)
to limit available HTTP methods to those given.
All possible Access-Control-Allow-XXX
headers are supported
by kcgi.
Complex Pre-flight Handling
All of the above can just as easily be enacted by using a reverse proxy such as relayd(8) or, for heavy-weight users married to Docker, nginx. In most web applications, responses are going to be mapped from specific origins to specific operations.
In the general case, a strategy is to contain all possible responses in a well-defined structure.
This can then be defined over all possible resources.
The response can then condition on the page
field in
struct kreq
and deliver any matching headers
(KRESP_ACCESS_CONTROL_ALLOW_HEADERS
,
KRESP_ACCESS_CONTROL_ALLOW_METHODS
, and
KRESP_ACCESS_CONTROL_ALLOW_CREDENTIALS
, respectively),
or inhibit the header if the value is NULL
as appears above
with the headers
variable.
This assumes that the origin is being set opaquely from the request.
Filtering on an origin request is just as easy. If origins may be
hard-coded or read from a file at startup, the KREQU_ORIGIN
value may be compared with an array of known origins.
In the most complex cases, the resps
array above may be
rendered multi-dimensional to map individual origins to individual
resources.
If a given origin is not known, simply do not respond with a
KRESP_ACCESS_CONTROL_ALLOW_ORIGIN
header.