CORS Explained: Cross-Origin Resource Sharing and Browser Security

How CORS works — preflight requests, Access-Control headers, why browsers block cross-origin requests, and how to configure CORS correctly for your API.

corssecuritynetworkingbrowsersapi

CORS (Cross-Origin Resource Sharing)

CORS is a browser security mechanism that controls which web applications on one origin (domain) are allowed to request resources from a different origin, using HTTP headers to grant or deny access.

What It Really Means

When your frontend at https://app.example.com makes an API call to https://api.example.com, the browser blocks it by default. This is the Same-Origin Policy — a fundamental browser security feature that prevents malicious websites from making requests to your bank's API using your cookies.

CORS is the mechanism for relaxing this restriction. The API server sends HTTP headers telling the browser: "I allow requests from app.example.com." Without these headers, the browser blocks the response even though the server processed the request successfully.

CORS is confusing because it is enforced by the browser, not the server. The server always receives and processes the request. The browser decides whether to let your JavaScript see the response. This is why the same API works fine from Postman or curl but fails from a browser — those tools do not enforce CORS.

How It Works in Practice

Simple Requests (No Preflight)

For simple requests (GET, POST with standard content types), the browser sends the request directly and checks CORS headers on the response:

Preflight Requests

For "non-simple" requests (PUT, DELETE, custom headers, JSON content type), the browser sends an OPTIONS preflight request first:

Key CORS Headers

HeaderPurposeExample
Access-Control-Allow-OriginWhich origins can accesshttps://app.example.com
Access-Control-Allow-MethodsWhich HTTP methods allowedGET, POST, PUT, DELETE
Access-Control-Allow-HeadersWhich request headers allowedAuthorization, Content-Type
Access-Control-Allow-CredentialsAllow cookies/authtrue
Access-Control-Max-AgeCache preflight (seconds)86400
Access-Control-Expose-HeadersWhich response headers JS can readX-Request-Id

Implementation

Express.js CORS configuration:

javascript

Nginx CORS headers:

nginx

Trade-offs

Security vs convenience:

  • Access-Control-Allow-Origin: * allows any website to access your API. Never use this with credentials: true.
  • Restricting to specific origins requires maintaining an allowlist. Dynamic validation is more flexible but more complex.*

Preflight overhead:

  • Every non-simple request incurs an extra OPTIONS round trip
  • Access-Control-Max-Age caches preflight results (set to 86400 for 24 hours)
  • In high-frequency API calls, preflight overhead is measurable

Avoiding CORS entirely:

  • Same-origin deployment: Serve API and frontend from the same domain
  • Reverse proxy: Nginx proxies /api/* to the backend, making it same-origin from the browser's perspective
  • BFF (Backend For Frontend): A same-origin backend that proxies API calls*

Common Misconceptions

  • "CORS protects the server" — CORS protects the user's browser. The server always processes the request. CORS prevents malicious JavaScript from reading the response.
  • "CORS errors mean the request failed" — The server received and processed the request. The browser blocked JavaScript from seeing the response. Check server logs — the request succeeded.
  • "Access-Control-Allow-Origin: * with credentials works" — It does not. Browsers reject this combination. You must specify the exact origin when using credentials.
  • "Disabling CORS in the browser is a valid fix" — It is a development workaround only. It removes a security protection for the user. The server must send correct CORS headers in production.
  • "Server-to-server calls need CORS" — CORS is a browser-only mechanism. Requests from servers (curl, Postman, backend services) are not subject to CORS restrictions.*

How This Appears in Interviews

  1. "Your frontend gets a CORS error calling your API" — Check that the API server sends correct Access-Control-Allow-Origin header. For non-simple requests, ensure OPTIONS preflight returns the right headers.
  2. "Design a public API" — Discuss CORS policy for third-party consumers. Rate limiting and API keys replace CORS for server-to-server authentication.
  3. "How do you secure an API that serves multiple frontends?" — Dynamic origin validation against an allowlist, credentials mode for authenticated requests, same-site cookies.
  4. "What is a preflight request?" — Browser sends OPTIONS before non-simple requests to check if the server allows the method, headers, and origin.

Related Concepts

GO DEEPER

Learn from senior engineers in our 12-week cohort

Our Advanced System Design cohort covers this and 11 other deep-dive topics with live sessions, assignments, and expert feedback.