- Published on
CORS (Cross-Origin Resource Sharing)
- Authors
- Name
- Aleksandar Zeljković
- @a_zeljkovic
What is CORS?
CORS, or Cross-Origin Resource Sharing is a browser security mechanism that controls page access to different origin resources due to a browser's same-origin policy. CORS restricts cross-origin requests in order to prevent (potentially) harmful requests towards server. Before we proceed, let's clarify what is the 'origin' in the context of the web (source: MDN):
Web content's origin is defined by the scheme (protocol), hostname (domain), and port of the URL used to access it. Two objects have the same origin only when the scheme, hostname, and port all match.
In practice, when the browser is sending the cross-origin request, it expects the information about the allowed origins from the server. However, the browser does not expect this information for every request, but only for requests that fulfill certain complexity criteria (qualified as "to be preflighted"):
- any method besides GET, HEAD and POST
- contains any header besides
Accept
,Accept-Language
, orContent-Language
- contains any
Content-Type
headers besidesapplication/x-www-form-urlencoded
,multipart/form-data
, ortext/plain
If the complexity criteria above is fulfilled for a given request, the browser is sending something called a preflight request to determine the server's allowed origin. Preflight request is just a plain OPTIONS request sent by the browser prior to the cross-origin request (with the sufficient complexity). OPTIONS HTTP requests are used to retrieve the endpoint metadata, like allowed methods, Access-Control-Allow-Origin
, Access-Control-Allow-Headers
and Access-Control-Max-Age
headers, etc. Once the server responds to the preflight request, browser is able to determine if the server is aware of the specified origin, methods and headers. Based on this response, the browser can either continue with the intended request, or throw a CORS error if the request doesn't fulfill these criteria.
How to handle CORS?
CORS handling is done on the server itself. It is done via proper configuration of CORS related headers and by setting the list of allowed requester origins.
Also, there are a couple of ways to avoid the CORS preflight checks:
- simplify the request to not fulfill the complexity criteria (if possible) — in this case, browser doesn't do preflight check
- enable caching via
Access-Control-Max-Age
header - access your backend via proxy - browser will send a request to a same-origin address of the proxy, and the proxy will forward the request to a cross-origin address
- send the request from an iframe — similar to a proxy solution, you're moving your frontend part of the code to an iframe which is the same-origin as the backend
Example
Let's have a look at the practical example through a web application consisting of a simple pure Node.js server and a frontend that will make a request to it. The frontend and the backend will have different origins, since they will listen on different ports (5050 vs 8080).
First, we're going to initialize an empty project:
pnpm init
Then, we're going to install our only dependency - https://github.com/vercel/serve, simple static file server for Node.js:
pnpm install serve
const http = require('node:http')
const HOST = 'localhost'
const PORT = 8080
const server = http.createServer((req, res) => {
res.statusCode = 200
res.setHeader('Content-Type', 'text/plain')
res.end('Hello from the server!')
})
server.listen(PORT, HOST, () => {
console.log(`🚀 Server is running @ http://${HOST}:${PORT}`)
})
Let's start the server by running node server.js
.
Frontend will be a plain HTML file that will make a call to our server immediately when the page is loaded:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
<title>App</title>
</head>
<body>
<h1>Welcome!</h1>
<script src="./index.js"></script>
</body>
</html>
where the content of index.js
is the following:
const HOST = 'localhost'
const PORT = 8080
async function getData() {
let response = await fetch(`http://${HOST}:${PORT}`)
let text = await response.text()
console.log(text)
}
getData()
To run our frontend, we will use serve
which we previously installed:
pnpm exec serve -p 5050 app
(assuming our files are called index.html
/index.js
, and are placed inside the app
folder)
Now, our frontend is running on http://localhost:5050
. If we open this URL in the browser and open the dev tools, we can see the following:
Since our frontend and backend are served on a different host (different port in this case), and the server is not sending any headers that inform frontend about allowed origin, the CORS error is thrown.
To handle this error, we need to specify the list of allowed request origins on our server via Access-Control-Allow-Origin
header. This can be done in several ways:
- set the specific origin you want to allow:
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:5050')
- allow any origin:
res.setHeader('Access-Control-Allow-Origin', '*')
- logic which handles a list of allowed origins:
const allowedOrigins = [
'http://localhost:5050',
'http://localhost:8020',
'http://example.com',
'https://example.com',
]
const origin = req.headers.origin
if (allowedOrigins.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin)
}
This is not the way you would build a production server and certainly not the way you would handle CORS, but the purpose of this example is to demonstrate a CORS error without going too much into unnecessary details.
If we add any of the mechanisms above to the server (and restart it), the browser will be informed which request origins are allowed for a given server and enable browser to make a request:
Full example repository can be found at: https://github.com/azeljkovic/cors
Resources
- w3 CORS specification: https://www.w3.org/TR/2020/SPSD-cors-20200602/
- MDN docs for CORS: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
- MDN docs for Preflight requests: https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request