Skip to content
Dev Tools Article

Why Developers Still Fail to Understand CORS

CORS is not a server firewall, but a browser-enforced relaxation of the Same-Origin Policy designed to protect users.

Lenn Voss
Lenn Voss
Cloud & Infrastructure Writer · Jun 21, 2026 · 5 min read
Why Developers Still Fail to Understand CORS

Every web developer has experienced the sudden, jarring halt of a console error: Access to fetch at '...' from origin '...' has been blocked by CORS policy. It usually happens late in a sprint, right when you're trying to wire up a new single-page app (SPA) to a microservice.

The immediate, exhausted reaction is almost universal: search Stack Overflow, find a snippet of middleware, and slap a wildcard onto the backend until the red text goes away.

But this "just make it work" dance reveals a fundamental, industry-wide misunderstanding. Cross-Origin Resource Sharing (CORS) is not a security wall built to protect your server. It is a browser-enforced mechanism designed to safely relax the Same-Origin Policy (SOP) to protect the user. When developers treat CORS as a nuisance to be bypassed rather than an architectural contract, they don't secure their systems—they actively expose their users to catastrophic exploits.

The Same-Origin Policy and the "Read vs. Write" Fallacy

To understand CORS, we have to go back to 1995 and the introduction of the Same-Origin Policy (SOP). SOP dictates that scripts running on one origin (defined as the exact combination of scheme, domain, and port) cannot access data from another origin.

If your frontend is running on https://myapp.com and your API is at https://api.myapp.com, they are different origins. Under strict SOP, the browser will block the frontend from reading the API's response.

But here is the crucial nuance that many developers miss: SOP restricts reading, not sending.

If a user is logged into bank.com and has a session cookie stored in their browser, and they accidentally visit malicious-site.com, a script on the malicious site can still initiate a POST request to bank.com/transfer. Because of how browsers handle cookies historically, those session cookies will be sent along with the request.

SOP does not stop the request from hitting the bank's server and executing. What SOP does is prevent the script on malicious-site.com from reading the response (e.g., confirming the transfer succeeded or reading the account balance). To prevent the actual execution of unauthorized actions, you need separate defenses like CSRF tokens, SameSite cookie attributes, or proper authentication headers.

CORS was introduced not to add security, but to relax this strict reading restriction in a controlled way. It allows the server at api.myapp.com to tell the browser: "Hey, I trust https://myapp.com. You can let its JavaScript read my responses."

The Zoom Localhost Disaster: A Masterclass in Bypassing CORS

When developers don't understand this contract, they build dangerous workarounds. The most infamous example of this is the 2019 Zoom vulnerability.

To provide a seamless user experience where clicking a web link automatically launched the native desktop application, Zoom ran a local web server on the user's machine listening on localhost:19421.

When a user loaded a Zoom launch page, the web page needed to communicate with this local server. However, instead of making a standard AJAX request and configuring CORS properly, Zoom's engineers implemented a bizarre hack: they loaded an image from the local server, encoding status and error codes directly into the dimensions of the returned image file.

Why did they do this? They likely assumed that the browser's CORS policy would block standard AJAX requests to localhost, or they simply wanted to bypass the browser's security boundaries entirely. In reality, modern browsers like Google Chrome and Mozilla Firefox fully support CORS on localhost.

By using an image-loading hack to bypass CORS, Zoom bypassed the browser's origin-filtering mechanism entirely. Because the local server didn't validate the origin of the request, any malicious website on the internet could load that same image, trigger commands on the local Zoom client (like turning on the user's camera and microphone), and read the status via the image dimensions.

A secure implementation would have been trivial: run a REST API on localhost:19421 and return the header Access-Control-Allow-Origin: https://zoom.us. This would instruct the browser to allow only JavaScript running on Zoom's official domain to interact with the local client, keeping the rest of the web locked out.

The Developer's Guide to Doing CORS Right

If you are building APIs or SPAs, you cannot afford to treat CORS as an afterthought. Here is how to handle it in production without introducing vulnerabilities or tanking your application's performance.

1. Avoid the Wildcard Trap

The easiest way to make CORS go away is setting Access-Control-Allow-Origin: *. However, if your API requires credentials (cookies, authorization headers, or TLS client certificates), browsers will flatly reject this.

If you set Access-Control-Allow-Credentials: true, the browser requires Access-Control-Allow-Origin to be an explicit, single origin. It cannot be *.

If you need to support multiple frontend origins (e.g., a staging environment and a production environment), do not hardcode a wildcard. Instead, write middleware on your server that inspects the incoming Origin request header, validates it against an allowlist, and dynamically mirrors that origin in the response header.

Here is how you would implement this securely in Express:

const allowedOrigins = [
  'https://myapp.com',
  'https://staging.myapp.com',
  'http://localhost:3000'
];

app.use((req, res, next) => {
  const origin = req.headers.origin;
  if (allowedOrigins.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
    res.setHeader('Access-Control-Allow-Credentials', 'true');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  }
  
  // Handle preflight
  if (req.method === 'OPTIONS') {
    return res.sendStatus(204);
  }
  next();
});

2. Tame the Preflight Request (OPTIONS)

For "non-simple" requests—such as those using PUT, DELETE, or custom headers like Content-Type: application/json—the browser will automatically send a "preflight" request using the OPTIONS method before sending the actual request.

This preflight checks if the server permits the cross-origin action. While secure, this adds a massive performance penalty: every API call now requires two round-trips to the server.

To mitigate this, always configure your server to return the Access-Control-Max-Age header. This tells the browser how long (in seconds) it can cache the preflight response before needing to ask again:

res.setHeader('Access-Control-Max-Age', '86400'); // Cache preflight for 24 hours

3. Remember: CORS Is Not a Server Firewall

Never rely on CORS to protect your backend resources from unauthorized access. Because CORS is enforced entirely by the browser, non-browser clients like curl, Postman, or a malicious script running in Node.js bypass CORS entirely. They do not send an Origin header, and they do not care what Access-Control-Allow-Origin headers your server returns.

Your API endpoints must still be secured with robust authentication (like JWTs or session tokens) and strict authorization checks on every single request.

Stop Bypassing, Start Configuring

CORS is often treated as a frustrating hurdle, but it is actually a vital bridge. It allows us to build rich, distributed web applications across multiple domains without throwing the user's browser security out the window.

The next time you see a CORS error in your console, don't look for a hack to bypass it. Treat it as a reminder of the security contract between your server, the browser, and the user. Configure your origins explicitly, cache your preflights, and keep your credentials secure.

Sources & further reading

  1. Developers don't understand CORS (2019) — fosterelli.co
  2. Developers don't understand CORS — changelog.com
  3. Developers don’t understand CORS (2019) – Kamal Reader — rss.boorghani.com
  4. Understanding CORS: A Practical Guide - DEV Community — dev.to
  5. Understanding CORS: A Developer's Guide — stack.convex.dev
Lenn Voss
Written by
Lenn Voss · Cloud & Infrastructure Writer

Lenn writes about cloud platforms, Kubernetes internals, and the infrastructure decisions that quietly make or break engineering organizations. Based in Berlin's vibrant tech scene, they have a talent for turning dense platform-engineering topics into prose that people actually finish reading.

Discussion 0

Join the discussion

Sign in or create an account to comment and vote.

No comments yet

Be the first to weigh in.

Related Reading