CORS, Preflight request and OPTIONS Method
Tags: beginners, webdev, security, cors
Published: 2022-04-10 20:46:17

## 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](https://developer.mozilla.org/en-US/docs/Web/Security/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](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/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)](https://github.com/didof/cors-post/tree/main/same-origin) you'll find.

###### `server.js`
```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`
```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 to `http://api.foo.it/bar`

<br />

> [Accompanying source code (cross-origin)](https://github.com/didof/cors-post/tree/main/cross-origin)

###### `index.html`

```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](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#preflighted_requests) 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/66trncnx3tiriqw9hiva.JPG)

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

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

> I use [fastify](https://www.fastify.io/) 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`.

![access-control-allow-origin: http://localhost:8000](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/goec10sqsflv8h8qrjlx.JPG)

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](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests).

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

> [Accompanying source code (cross-origin-preflight)](https://github.com/didof/cors-post/tree/main/cross-origin-preflight)

###### `index.html`
```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.

![request 1 = localhost, request 2 = me (preflight), request 3 = me (actual request)](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9je3b8x0lxt8v6cobeh7.JPG)

The method used is [`OPTIONS`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/agasceg6vhmuv75ymziy.JPG)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zxn7rxilrcei0pweldkr.JPG)

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.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3u4ga92b03q8u39idqrl.JPG)

***

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](https://auth0.com/blog/cors-tutorial-a-guide-to-cross-origin-resource-sharing/)
- [Same-origin policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy)
- [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)
- [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#supplying_your_own_request_object)
- [Accompanying GitHub source code](https://github.com/didof/cors-post)

### Get in touch
- [🐔 Twitter](https://twitter.com/did0f)
- [🐙 GitHub](https://github.com/didof)
- [💼 LinkedIn](https://www.linkedin.com/in/didof/)