Francesco Di Donato
Posted on April 10, 2022
Premise
This post is intended to be a light reading with the purpose to give a minimum of context and instill some curiosity towards a topic often considered opaque - CORS are a simple HTTP-header mechanism that every web developer can easily understand.
First, you need to be aware of the Same-origin Policy. It is a browser built-in mechanism which restricts how a script on one origin interacts with a resource on another.
Same-origin 🏠🏠
Let's start with the simplest case. Both the server that holds the resource and the client that requests it reside under the same origin (<scheme>://<hostname><port>
).
The server is exposed at http://localhost:3000
and responds to HTTP GET /
by returning an HTML file.
For the sake of brevity, the code examples only show what I want to focus on. If something is not clear, use the various Accompanying source code (same-origin) you'll find.
server.js
server.get("/", (_, reply) => {
reply.sendFile("index.html");
});
server.get("/me", (_, reply) => {
reply.status(200).send({ nickname: "didof" });
});
The HTML arrived on the client asks the server on the same origin for the resource associated with the route /me
and shows the nickname on the screen.
index.html
<p>Nickname: <output></output></p>
<script>
// This runs on http://localhost:3000
fetch("/me") // fetch("http://localhost:3000/me")
.then((res) => res.json())
.then((res) => {
document.querySelector("output").textContent = res.nickname;
});
</script>
The payload is accessible as same-origin policy is respected. No need for CORS, no need for preflight request.
Cross-origin 🏠🏭
Now the client is exposed on http://localhost:8000
while the server with the resource is exposed on http://localhost:3000
. Even if scheme and hostname are the same, the ports are different - therefore they are two different origins.
In the real world, the following concept is found when the client on
https://mywebsite.com
requires a resource tohttp://api.foo.it/bar
index.html
<script>
// This runs on http://localhost:8000
const request = new Request("http://localhost:3000/me", {
method: "GET",
});
fetch(request)
.then((res) => res.json())
.then((res) => { ... })
</script>
As you can guess, you have a CORS error.
The error in the console clearly explains the problem. Let's fix it. On the server, we manage CORS.
server.js
server.register(fastifyCors, {
origin: "http://localhost:8000",
});
I use fastify here, but any framework offers its own way of managing CORS. It's just headers added to the response, nothing more - you can of course append them manually.
Now, the response will include the access-control-allow-origin
and the value will be http://localhost:8000
.
The server is basically giving permission to the browser which issued the request to unfold the response.
Preflight Request 🏠✉️🏭
There are two types of CORS request:
- Simple request
- Preflight request
Which is used is determined by the browser. Up to this moment the client has carried out simple requests because they fit the criteria.
For the purpose of this post, you only need to know that to force the browser make a preflight request you just need to add to the request a custom header.
index.html
<script>
const headers = new Headers();
headers.append("X-CUSTOM-HEADER", "foo");
// This runs on http://localhost:8000
const request = new Request("http://localhost:3000/me", {
method: "GET",
headers,
});
fetch(request)
.then((res) => res.json())
.then((res) => { ... })
And that's enough for the browser to fire two requests instead of one.
The method used is OPTIONS
, which is interpreted by the server as a query for information about the defined request url
.
The browser also appends some headers to the preflight request. Access-Control-Request-Headers
and Access-Control-Request-Method
with their relative values.
The browser is asking permission to the server to make a GET
request which has among the various headers also a certain X-CUSTOM-HEADER
.
This particular server, configured to be extremely permissive, responds with access-control-allow-headers
whose value mirrors the requested one and access-control-allow-methods
which tells that all methods are allowed.
It's giving a big thumbs up 👍. The browser then can perform the wanted request.
Obviously, if for some reason the server was instructed not to allow the presence of the custom header you would get an error.
As mentioned, this is but a simple chat. If interested, below are some references to go further. I 💖 the H of HTML!
Resouces 🕳️🐇
- CORS Tutorial: A Guide to Cross-Origin Resource Sharing
- Same-origin policy
- CORS
- Fetch API
- Accompanying GitHub source code
Get in touch
Posted on April 10, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.