In-depth Analysis and Understanding of CORS — Cross-Origin Resource Sharing
This article provides a detailed explanation of Cross-Origin Resource Sharing (CORS), its purpose, and how it manages cross-origin requests on the browser. It covers preflight requests, HTTP headers, and potential misconfigurations.
In-depth Analysis and Understanding of CORS — Cross-Origin Resource Sharing
In the course of developing modern web applications, it's common for systems to communicate across different domains. Take, for instance, a scenario where your frontend application is hosted at app.com
and it interacts with a backend API residing at api.com
.
The browser perceives these as two distinct origins and for the sake of security, implements a stringent rule:
JavaScript is denied from making cross-origin requests unless the server permits it explicitly.
This security mechanism is known as CORS — Cross-Origin Resource Sharing.
The Underlying Principle of CORS
The primary purpose of CORS is to safeguard users from potential threats that might arise from malicious websites intending to:
- Extract sensitive data from other origins
- Make API calls using the user's authentication
- Exploit Cross-Site Request Forgery (CSRF) attacks through XHR or fetch
In the absence of CORS, any web page could potentially call any API and read the response using the user’s credentials, leading to serious security breaches.
Defining “Cross-Origin”
In the context of web security, an origin is demarcated by the combination of scheme (protocol) + domain + port.
For example:
https://api.example.com
≠https://example.com
http://localhost:3000
≠http://localhost:5000
https://example.com
≠http://example.com
Hence, even the same domain with a different protocol or port is considered cross-origin.
CORS Explained Through Code
Simple Request
Consider the following JavaScript fetch request:
fetch("https://api.example.com/data")
The browser performs a series of checks:
- It verifies if the method is
GET
,POST
, orHEAD
. - It checks if the headers are simple (i.e., it does not contain any custom headers).
- It examines if the response is permitted via the
Access-Control-Allow-Origin
header.
If the checks pass and the server allows the request, the server responds with the Access-Control-Allow-Origin
header:
Access-Control-Allow-Origin: https://frontend.example.com
Preflight Requests in CORS
In scenarios where a request:
- Utilizes methods such as
PUT
,DELETE
- Sends custom headers
- Possesses a content type like
application/json
The browser sends a preflight request, which is an HTTP OPTIONS request, to the server:
OPTIONS /data HTTP/1.1
Origin: https://frontend.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type
The server must respond with appropriate Access-Control-*
headers:
Access-Control-Allow-Origin: https://frontend.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type
Allowing Credentials in CORS
To allow cookies to be sent with the request, the server must include the Access-Control-Allow-Credentials
header:
Access-Control-Allow-Credentials: true
And the frontend must explicitly enable credentials in the fetch request:
fetch(url, { credentials: "include" });
Note: When using credentials, Access-Control-Allow-Origin
cannot be *
.
Common Misconfigurations in CORS
- Setting
Access-Control-Allow-Origin: *
while also using cookies. This is a security risk as it allows any origin to read the response. - Neglecting to respond to
OPTIONS
requests. This will cause preflighted requests to fail. - Dynamically allowing origins without validation (e.g.,
res.set('Access-Control-Allow-Origin', req.headers.origin)
). This could allow malicious sites to bypass the CORS policy. - Not setting
Vary: Origin
which can break caching because it tells the browser to cache different CORS responses for different origins.
Configuring CORS: Application Examples
Express.js (Node)
In an Express.js application, you can use the cors
middleware to enable CORS:
const cors = require("cors");
app.use(cors({
origin: "https://frontend.example.com",
credentials: true
}));
NGINX
In an NGINX server configuration, you can use the add_header
directive:
location /api/ {
add_header Access-Control-Allow-Origin https://frontend.example.com;
add_header Access-Control-Allow-Credentials true;
}
Real-World Example: Stripe
Stripe’s API only allows CORS for:
- Public
fetch()
APIs (e.g., Elements, webhooks) - Origins that are part of Stripe’s official frontend products
Requests from arbitrary websites to https://api.stripe.com
are blocked unless explicitly allowed.
Tools for Debugging CORS Issues
- Chrome DevTools → Network → XHR → Headers/Console can be used to inspect the request and response headers.
- CORS Anywhere can be used for testing with proxies, by making requests through a server that adds the necessary CORS headers.
- Postman doesn't enforce the CORS policy and can be used to make requests directly to the server.
- curl can simulate different request headers and methods.
Anti-Patterns in CORS
- Allowing
Access-Control-Allow-Origin: *
on authenticated endpoints. This is a security risk as it allows any origin to read the response. - Ignoring preflight headers. This can lead to unexpected behavior as the server may not be prepared to handle the actual request.
- Not responding to OPTIONS with correct CORS headers. This will cause preflighted requests to fail.
- Not logging failed origin checks. This can help in troubleshooting and identifying potential security breaches.
Conclusion: Trust, but Verify
CORS is akin to the browser’s built-in firewall. It aims to prevent unauthorized data leaks, token theft, and abuse of credentials.
As a frontend developer:
- Be aware of what your API server expects in terms of CORS.
- Understand what triggers a preflight request.
- Test every fetch request, not just in your development environment.
And as a backend developer:
- Whitelist only trusted origins.
- Treat CORS as a security boundary — because it indeed is.
Subscribe for Deep Dives
Get exclusive in-depth technical articles and insights directly to your inbox.
Related Posts
Timeouts — Mastering Network Resilience and Responsiveness
In this article, we delve into the significance of timeout strategies in frontend networking, exploring how to efficiently cancel slow requests, provide early error feedback, and construct more robust and responsive web applications.
Demystifying XSS (Cross-Site Scripting): A Comprehensive Approach for Frontend Developers
Explore the intricacies of Cross-Site Scripting (XSS), one of the most pervasive and perilous vulnerabilities in web applications. We delve into how XSS operates, its types, real-world examples, and effective prevention strategies, ensuring a fortified frontend development strategy.
HTTPS — The Core Pillar of Modern Web Security
HTTPS isn't just a protocol, it's the foundation that ensures the security, integrity, and trustworthiness of data exchange on the web. Dive into the depths of HTTPS and understand why it’s non-negotiable for modern web development.