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.
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.
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)
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
andAccess-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>
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.
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.
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};
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.
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.