Overview

In web development, you may come across the message “CORS error”. In this article, we’ll take a look at what CORS is, why it’s important, and how it applies to web development.

CORS?

CORS stands for Cross-Origin Resource Sharing and is a security mechanism that allows resources from different origins to be shared. When a web page requests a resource from another domain, the browser follows the Same-Origin Policy. This policy restricts access to resources from different sources for security reasons. CORS allows you to relax some of these restrictions.

Untitled

The web browser checks the headers of the response coming from the server and spits out a CORS error if `Access-Control-Allow-Origin has a different origin.

Untitled

Same-origin policy (SOP)

The same-origin policy is one of the core principles of web security. This policy essentially prohibits web pages from accessing resources from different origins. Here, “**origin” is a concept that includes the protocol, **host (domain), and port.

URL components

A URL has the following components

  • Schema (Scheme, Protocol)
  • Authority, Host : Domain Name + Port
  • Resource Path (Resource Path)
  • Parameter (Parameter, Query Param)
  • 앵커 (Anchor)

Untitled

CORS Workaround

Setting the CORS header on the server side

  • You can set to allow requests from specific origins by including the Access-Control-Allow-Origin header in the server response.
  • Example: Access-Control-Allow-Origin: * will allow requests from any origin. This may not be recommended for security reasons, so we recommend explicitly specifying specific domains if necessary.

Using a proxy server

  • Place a proxy server between your web application and the API server to relay requests. The proxy server forwards the client’s request to the API server by setting the CORS header.
  • This method is useful when the client and API server are in different domains.

Preflight request processing

  • For complex HTTP requests (such as PUT, DELETE, or requests that use specific HTTP headers), the browser must send a preflight request and the server must authorize it.
  • The server can authorize these preflight requests using the Access-Control-Allow-Methods and Access-Control-Allow-Headers headers.

CORS Practice

Let’s try to implement the CORS workaround mentioned above in code. First, we’re going to create a simple NODE server and HTML client page.

Preparation

There are various ways to install the below, so please refer to them.

Reproducing CORS errors

If you installed the express package using node server and npm, it will write the server code.

This is really simple code that receives and responds to requests with GET and PUT methods. In the case of PUT requests, it receives them in text/plain and outputs them as they are.

  • server.js
 1const express = require('express');
 2const app = express();
 3
 4app.use(express.text())
 5
 6app.get('/data', (req, res) => {
 7    res.json({ message: 'Hello from server!' });
 8});
 9
10app.put('/register', (req, res) => {
11    const requestData = req.body;
12    res.json({ message: 'message is ' + requestData });
13});
14
15app.listen(3000, () => {
16    console.log('Server running on port 3000');
17});

Here’s a simple web page to request from the server. It’s a simple page with two buttons, each of which, when pressed, displays a corresponding message in an Alert window.

  • client.html
 1<!DOCTYPE html>
 2<html>
 3<head>
 4    <title>CORS Test</title>
 5</head>
 6<body>
 7<h1>CORS Test</h1>
 8<button id="fetchButton">Fetch Data</button>
 9<button id="putButton">Send PUT Request</button> <!-- 추가된 버튼 -->
10<script>
11    document.getElementById('fetchButton').addEventListener('click', () => {
12        fetch('http://localhost:3000/data')
13                .then(response => response.json())
14                .then(data => alert(JSON.stringify(data)))
15                .catch(error => alert('CORS Error: ' + error));
16    });
17
18    document.getElementById('putButton').addEventListener('click', () => {
19        const requestOptions = {
20            method: 'PUT',
21            headers: {
22                'Content-Type': 'text/plain',
23            },
24            body: 'Hello World',
25        };
26
27        fetch('http://localhost:3000/register', requestOptions)
28                .then(response => response.json())
29                .then(data => alert(JSON.stringify(data)))
30                .catch(error => alert('CORS Error: ' + error));
31    });
32</script>
33</body>
34</html>

Untitled

When we hit Fetch Data and Send PUT Request with this setup, we get a CORS error. Not surprisingly, the browser throws a CORS error because the server isn’t pointing us to any headers.

Untitled

Solved by setting response headers on the server

Now that we know when CORS is throwing errors, we need to work on adjusting the header values on the server to prevent CORS errors from occurring. First, add the CORS middleware to your node to add an origin and specify the methods that should be allowed. To see the difference between the two, we’ll only allow GET.

First, install the cors package from npm.

1$ npm install cors                                      INT  3m 12s 
2
3added 2 packages, and audited 65 packages in 434ms
4
511 packages are looking for funding
6  run `npm fund` for details
7
8found 0 vulnerabilities

Then, modify the server.js you’ve already written to look like this

 1const express = require('express');
 2const cors = require('cors');
 3
 4const app = express();
 5
 6app.use(express.text())
 7
 8const corsOptions = {
 9    origin: 'http://localhost:8080', // Set which sources to allow
10    methods: ['GET'] // Set HTTP methods to allow
11};
12
13app.use(cors(corsOptions));
14
15app.get('/data', (req, res) => {
16    res.json({ message: 'Hello from server!' });
17});
18
19app.put('/register', (req, res) => {
20    const requestData = req.body;
21    res.json({ message: 'message is ' + requestData });
22});
23
24app.listen(3000, () => {
25    console.log('Server running on port 3000');
26});

We’ll set Origin to port 8080 and only allow the GET method. One thing to consider when testing is that if you open the html as a file, the Origin header will be set to null, so it’s best to test by serving the html page through a separate http server. In my case, I started a static server using node’s http-server package in the folder with client.html.

 1http-server                                                ok  3s 
 2Starting up http-server, serving ./
 3
 4http-server version: 14.1.1
 5
 6http-server settings: 
 7CORS: disabled
 8Cache: 3600 seconds
 9Connection Timeout: 120 seconds
10Directory Listings: visible
11AutoIndex: visible
12Serve GZIP Files: false
13Serve Brotli Files: false
14Default File Extension: none
15
16Available on:
17  http://127.0.0.1:8080
18  http://192.168.0.29:8080
19  http://10.250.0.4:8080
20Hit CTRL-C to stop the server

This way, you can serve the client.html we created above on the server, and when you do, you can see that the GET method passes, but the PUT method still throws a CORS error, as shown below.

Untitled

So let’s modify it again, this time to allow the PUT method, and do something like this.

1const corsOptions = {
2    origin: 'http://localhost:8080', // Set which sources to allow
3    methods: ['GET', 'PUT'] // Set HTTP methods to allow
4};

Untitled

When you actually run it, you’ll see that it works fine. But here’s the weird part: when you filter on All in DevTools, you’ll see that there’s only one type (other than Fetch/XHR), which is Preflight.

As mentioned above, certain methods or headers will make an OPTION request to the server before actually requesting the method, and the browser will determine whether or not to make the actual request by looking at the response header value coming back from that request.

Untitled

Conclusion

In this article, I’ve summarized what CORS is and what you can do to solve the problem.

I’ve also implemented an example code to help you understand it better, and if you don’t want to bother with it too much, you can use proxy middleware in node itself or a proxy server like nginx to bypass the CORS issue. However, this is not the intended direction of the browser, and it’s one more step to communicate, so you need to be careful when making this decision based on the context of your current project.