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.

ShareShare

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.comhttps://example.com
  • http://localhost:3000http://localhost:5000
  • https://example.comhttp://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, or HEAD.
  • 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.

about

Ehsan Hosseini

Ehsan Hosseini

me [at] ehosseini [dot] info

Staff Software Engineer and Tech Lead with a track record of leading high-performing engineering teams and delivering scalable, end-to-end technology solutions. With experience across web, mobile, and backend systems, I help companies make smart architectural decisions, scale efficiently, and align technical strategy with business goals.

© 2025 Ehsan Hosseini. All rights reserved.