What is CORS?
Cross-Origin Resource Sharing (CORS) is a browser-based security mechanism that controls how web applications (e.g., Single Page Applications or SPAs) can request resources from a different origin (domain, protocol, or port) than their own. It’s designed to prevent malicious websites from accessing sensitive data in a user’s browser without permission.
-
Origin: An origin is defined by the combination of protocol (e.g.,
https
), domain (e.g.,example.com
), and port (e.g.,443
). For example:-
https://app.example.com
andhttps://api.example.com
are different origins. -
https://example.com:8080
andhttps://example.com:3000
are different origins.
-
Same-Origin Policy: Browsers enforce this policy, which restricts web pages from making requests to a different origin unless explicitly allowed by the server. CORS is the mechanism that allows servers to relax this restriction.
Why Does CORS Exist?
CORS protects users from cross-origin attacks in browsers. For example:
- A malicious site (
evil.com
) could try to fetch data fromapi.bank.com
using a user’s browser, exploiting their active session (e.g., cookies or authentication tokens). - Without CORS, the browser would send the request, and if the server responds,
evil.com
could steal sensitive data. - CORS ensures the server specifies which origins (e.g.,
bank.com
) are trusted, and the browser blocks responses to untrusted origins.
Why is CORS Enforced at the Server Level?
CORS is implemented at the server level (not the web application or browser level) for these reasons:
-
Server Authority: The server hosting the API decides who can access its resources. It uses CORS headers (e.g.,
Access-Control-Allow-Origin
) to specify trusted origins. - Security: Clients (browsers or SPAs) can be manipulated or compromised. If CORS were enforced client-side, malicious scripts could bypass restrictions.
- Consistency: Server-level control ensures uniform access policies across all clients, avoiding inconsistent behavior.
- Non-Browser Clients: APIs often serve non-browser clients like CLIs, which aren’t subject to the Same-Origin Policy. Server-level CORS allows flexibility for these clients without affecting browser security.
Why Can CLIs Bypass CORS?
CLIs (e.g., curl
, Python scripts) don’t run in a browser, so they aren’t subject to the Same-Origin Policy or CORS restrictions. They can call any API endpoint directly, assuming the API is accessible (e.g., no authentication or firewall blocks).
- Why this is okay: CLIs don’t typically have access to a user’s browser session (e.g., cookies), so they can’t exploit browser-specific vulnerabilities. APIs use other security measures (e.g., API keys, OAuth) to secure access from non-browser clients.
- Server’s role: The server still controls access for CLIs through authentication, rate limiting, or IP whitelisting, separate from CORS.
How Does CORS Work?
-
Client Makes a Request: A web application (e.g., an SPA) sends an HTTP request to a different origin using
fetch
orXMLHttpRequest
. -
Server Responds with CORS Headers: The server includes headers like:
-
Access-Control-Allow-Origin
: Specifies which origins are allowed (e.g.,https://app.example.com
or*
for all origins). -
Access-Control-Allow-Methods
: Allowed HTTP methods (e.g.,GET
,POST
). -
Access-Control-Allow-Headers
: Allowed headers in requests.
-
-
Browser Checks Headers: The browser compares the requesting origin with the
Access-Control-Allow-Origin
header:- If the origin is allowed, the browser delivers the response to the web application.
- If not, the browser blocks the response, and the web application sees an error.
-
Preflight Requests: For certain requests (e.g.,
POST
with custom headers), the browser sends anOPTIONS
preflight request to check if the server allows the request. The server must respond with appropriate CORS headers.
Practical Example: CORS in Action
Let’s create a simple SPA that fetches data from an API, along with a server that demonstrates CORS behavior. We’ll also show how a CLI can access the same API without CORS restrictions.
Step 1: Set Up the API Server
We’ll create a simple Node.js server using Express that serves data and includes CORS headers.
const express = require('express');
const app = express();
// Middleware to add CORS headers
app.use((req, res, next) => {
// Allow requests only from http://localhost:3000
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
next();
});
// Sample API endpoint
app.get('/data', (req, res) => {
res.json({ message: 'Hello from the API!' });
});
// Handle preflight OPTIONS requests
app.options('/data', (req, res) => {
res.sendStatus(200);
});
app.listen(4000, () => {
console.log('API server running on http://localhost:4000');
});
Save this as server.js
. To run it:
- Install Node.js and Express:
npm install express
. - Run the server:
node server.js
.
This server:
- Allows CORS requests only from
http://localhost:3000
. - Responds to
GET /data
with a JSON message. - Handles preflight
OPTIONS
requests.
Step 2: Create the SPA
Create a simple HTML file with JavaScript to fetch data from the API. Serve it from http://localhost:3000
.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CORS Demo SPA</title>
</head>
<body>
<h1>CORS Demo</h1>
<button onclick="fetchData()">Fetch Data</button>
<p id="result"></p>
<script>
async function fetchData() {
try {
const response = await fetch('http://localhost:4000/data');
const data = await response.json();
document.getElementById('result').textContent = data.message;
} catch (error) {
document.getElementById('result').textContent = 'Error: ' + error.message;
}
}
</script>
</body>
</html>
Save this as index.html
. To serve it:
- Use a simple HTTP server like Python’s:
python -m http.server 3000
. - Open
http://localhost:3000
in a browser.
This SPA:
- Makes a
fetch
request tohttp://localhost:4000/data
. - Displays the API response or an error.
Step 3: Test CORS Behavior
-
Successful Request:
- Open
http://localhost:3000
in a browser and click “Fetch Data.” - Since the server allows
http://localhost:3000
(viaAccess-Control-Allow-Origin
), the request succeeds, and you’ll see:Hello from the API!
- Open
-
CORS Failure:
- Serve the same
index.html
from a different origin, e.g.,http://localhost:5000
(runpython -m http.server 5000
). - Open
http://localhost:5000
and click “Fetch Data.” - The browser will block the response because
http://localhost:5000
isn’t allowed by the server’s CORS headers. You’ll see an error like:CORS policy: No 'Access-Control-Allow-Origin' header
.
- Serve the same
-
CLI Access (No CORS Restrictions):
- Use a CLI tool to call the API:
curl http://localhost:4000/data
- Output:
{"message":"Hello from the API!"}
- The CLI request succeeds because CORS doesn’t apply outside browsers.
Step 4: Experiment with CORS Settings
Modify the server’s CORS headers to see different behaviors:
- Allow all origins: Change
Access-Control-Allow-Origin
to'*'
. Now, any origin (e.g.,http://localhost:5000
) can access the API from a browser. - Disable CORS: Remove the
Access-Control-Allow-Origin
header. Browser requests will fail, but CLI requests will still work.
Key Takeaways
- CORS is Browser-Only: It protects web applications from unauthorized cross-origin requests in browsers but doesn’t affect CLIs or other non-browser clients.
- Server-Level Enforcement: The server uses CORS headers to control access, ensuring security and consistency. Client-side enforcement would be insecure.
- CLI Flexibility: CLIs bypass CORS because they don’t run in browsers, but APIs use authentication or other mechanisms to secure non-browser access.
- Practical Use: CORS enables SPAs to safely fetch data from different origins while preventing malicious sites from exploiting user sessions.
Top comments (0)