When building modern web applications, client-side JavaScript running in a browser frequently needs to request resources or send data to an API hosted on another domain. Cross-Origin Resource Sharing (CORS) is the protocol mechanism that governs this interaction.

Core definition of CORS

Cross-Origin Resource Sharing (CORS) is an HTTP-header-based security mechanism managed by web browsers. It allows a server to explicitly declare which external origins are permitted to read its responses when accessed via client-side web scripts such as fetch() or XMLHttpRequest.

By default, web browsers restrict cross-origin network requests to protect users from malicious cross-site data exposure. CORS provides the controlled mechanism through which servers can grant access to trusted frontend origins.

Same-origin policy and origin definition

The foundation of browser security is the Same-Origin Policy (SOP). Under SOP, a web browser restricts scripts running on one origin from interacting with resources or data loaded from a different origin.

An origin is defined by three specific components:

  1. Scheme (the protocol, such as https:// or http://)
  2. Host (the domain or subdomain, such as app.example.test or api.example.test)
  3. Port (the communication port, such as :443 or :8080)

Two URLs share the same origin only if all three components match identically. For example:

  • https://app.example.test/dashboard and https://app.example.test/settings are same-origin.
  • https://app.example.test and http://app.example.test are cross-origin (different scheme).
  • https://app.example.test and https://api.example.test are cross-origin (different host).
  • https://app.example.test:443 and https://app.example.test:8443 are cross-origin (different port).

When client-side code on https://app.example.test initiates an API call to https://api.example.test, the browser identifies the request as cross-origin and applies CORS validation rules.

What CORS headers do

CORS relies on specific HTTP headers exchanged between the browser and the target server:

Request headers (sent automatically by the browser)

  • Origin: Informs the server of the scheme, host, and port of the web page initiating the request (e.g., Origin: https://app.example.test).
  • Access-Control-Request-Method: Used during preflight requests to announce which HTTP method will be used.
  • Access-Control-Request-Headers: Used during preflight requests to announce custom or non-safelisted request headers.

Response headers (configured on the server)

  • Access-Control-Allow-Origin: Specifies which requesting origin may access the response (e.g., https://app.example.test or *).
  • Access-Control-Allow-Methods: Specifies permitted HTTP methods for cross-origin requests.
  • Access-Control-Allow-Headers: Specifies permitted HTTP request headers.
  • Access-Control-Allow-Credentials: Indicates whether the browser may expose the response when credentials (such as cookies or authorization headers) are included.
  • Access-Control-Expose-Headers: Declares which non-safelisted response headers can be read by JavaScript.
  • Access-Control-Max-Age: Defines how long (in seconds) the browser may cache a preflight response.

What CORS does and does not protect

Understanding what CORS protects—and what it does not—is critical for designing secure web services:

  • What CORS protects: It prevents unauthorized client scripts in a user’s browser from reading sensitive responses returned by a cross-origin API.
  • What CORS does NOT protect:
    • CORS is not an authentication or authorization system.
    • CORS does not prevent a server from receiving or executing an HTTP request from non-browser clients (such as curl, Postman, server-to-server calls, or automated scripts) that do not enforce the Same-Origin Policy.
    • CORS is not a replacement for server-side access control, token verification, or session validation.

Simple requests and preflighted requests

The browser categorizes cross-origin requests into two primary groups based on risk and complexity:

Simple requests

A request is considered “simple” if it meets all of the following conditions:

  • Uses a safe method: GET, HEAD, or POST.
  • Contains only CORS-safelisted request headers (Accept, Accept-Language, Content-Language, Content-Type, or Range).
  • Uses a CORS-safelisted Content-Type: application/x-www-form-urlencoded, multipart/form-data, or text/plain.

Simple requests are sent directly to the server without prior approval. The browser then examines the server’s Access-Control-Allow-Origin response header before delivering the response payload to JavaScript.

Preflighted requests

If a request does not meet the simple request criteria—for instance, if it uses PUT or DELETE, sends custom headers like Authorization, or uses Content-Type: application/json—the browser automatically sends an OPTIONS preflight request first.

The browser evaluates the preflight response. Only if the server permits the origin, method, and headers does the browser transmit the actual intended request.

Common CORS debugging approach

When debugging cross-origin issues in web development:

  1. Examine browser DevTools Network panel: Inspect both the console error message and the exact network exchange to see whether a preflight OPTIONS request was sent and what response headers were returned.
  2. Compare origin values: Verify that the Origin sent by the browser matches the exact scheme, host, and port allowed in the server’s Access-Control-Allow-Origin header.
  3. Verify server header ownership: Remember that CORS headers must be configured on the server or API gateway. Frontend code cannot inject response headers into the server’s HTTP response.

Key takeaway

CORS is a browser-enforced security mechanism using HTTP headers that enables servers to explicitly allow cross-origin script access. It is not an authentication layer or a barrier against non-browser tools, and it requires server-side response header configuration to function correctly.