didof.dev

CORS, Preflight request and OPTIONS Method

CORS, Preflight request and OPTIONS Method

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.

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.

<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.


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.

Access to fetch at 'http://localhost:3000/me' from origin 'http://localhost:8000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled. GET http://localhost:3000/me net::ERR_FAILED 200

The error in the console clearly explains the problem. Letโ€™s fix it. On the server, we manage CORS.

server.register(fastifyCors, {
  origin: "http://localhost:8000",
});

Now, the response will include the access-control-allow-origin and the value will be http://localhost:8000.

access-control-allow-origin: 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.

<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.

request 1 = localhost, request 2 = me (preflight), request 3 = me (actual request)

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. Access-Control-Request-Headers: x-custom-header, Access-Control-Request-Method: GET

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.

access-control-allow-headers: x-custom-header, access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE

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.

Access to fetch at 'http://localhost:3000/me' from origin 'http://localhost:8000' has been blocked by CORS policy: Request header field x-custom-header is not allowed by Access-Control-Allow-Headers in preflight response.


As mentioned, this is but a simple chat. If interested, below are some references to go further. I ๐Ÿ’– the H of HTML!

Resouces ๐Ÿ•ณ๏ธ๐Ÿ‡