Understanding Fetch API and CORS in JavaScript

This article provides a comprehensive overview of the modern JavaScript Fetch API, explaining its core architecture and how it executes HTTP network requests. It specifically examines how the browser security model interacts with the Fetch API, detailing the mechanisms behind Cross-Origin Resource Sharing (CORS), preflight checks, request modes, and the configuration required to securely exchange data across different origins.


What is the Fetch API?

The Fetch API is a native JavaScript interface designed for accessing and manipulating HTTP pipeline components, such as requests and responses. Introduced as a modern replacement for XMLHttpRequest, Fetch is built around JavaScript Promises, offering a cleaner, more readable syntax for asynchronous network operations.

A basic fetch request looks like this:

fetch('https://api.example.com/data')
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    return response.json();
  })
  .then(data => console.log(data))
  .catch(error => console.error('Fetch error:', error));

Understanding the Same-Origin Policy and Cross-Origin Requests

Browsers enforce the Same-Origin Policy (SOP) to protect user data from malicious scripts. Two URLs share the same origin only if their protocol, domain (host), and port match exactly.

A request is considered cross-origin when a web page hosted on one origin (e.g., https://frontend.com) requests resources from a different origin (e.g., https://api.backend.com). By default, SOP blocks scripts from reading responses sent from another origin unless the server explicitly grants permission via Cross-Origin Resource Sharing (CORS) headers.


How the Fetch API Handles Cross-Origin Requests

The Fetch API handles cross-origin requests by integrating directly with the browser’s CORS mechanism. When a cross-origin request is triggered, the browser appends an Origin HTTP header indicating where the request initiated.

Origin: https://frontend.com

The target server must respond with appropriate CORS headers to authorize access. The most critical header is:

Access-Control-Allow-Origin: https://frontend.com

(or * to allow any domain, though wildcards cannot be used when sending user credentials).


Fetch Request Modes

The Fetch API accepts a mode configuration property that defines how cross-origin requests are treated:

  1. cors (Default): Enforces CORS validation. The browser expects valid CORS headers from the server. If the headers are missing or invalid, the request fails with a network error.
  2. no-cors: Limits the request to simple actions (like GET or POST with simple headers). JavaScript cannot access the response body or status code (the response type becomes opaque). This is primarily used for caching assets via Service Workers.
  3. same-origin: Rejects any request directed to a different origin immediately, preventing outgoing cross-origin network traffic.

Example specifying the mode:

fetch('https://api.backend.com/data', {
  method: 'GET',
  mode: 'cors'
});

Simple Requests vs. Preflighted Requests

The Fetch API distinguishes between two types of cross-origin requests:

1. Simple Requests

A request is treated as “simple” if: - It uses methods like GET, HEAD, or POST. - It only sets safe headers (e.g., Accept, Accept-Language, Content-Language). - The Content-Type is limited to application/x-www-form-urlencoded, multipart/form-data, or text/plain.

These requests are dispatched immediately with the Origin header attached.

2. Preflighted Requests

If a fetch request uses custom headers (e.g., Authorization), HTTP methods other than standard ones (e.g., PUT, DELETE, PATCH), or a Content-Type of application/json, the browser automatically sends an OPTIONS preflight request first.

The preflight request asks the server for permission before sending the actual request:

OPTIONS /data HTTP/1.1
Origin: https://frontend.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization

The server must reply with corresponding approval headers:

Access-Control-Allow-Origin: https://frontend.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization

Once confirmed, the browser executes the actual fetch call.


Handling Credentials in Cross-Origin Fetch

By default, cross-origin fetch requests do not send or receive HTTP cookies, HTTP authentication, or client-side SSL certificates. To include credentials, set the credentials property:

fetch('https://api.backend.com/user-profile', {
  method: 'GET',
  credentials: 'include' // Options: 'omit', 'same-origin', 'include'
});

When credentials: 'include' is used: - The server must respond with Access-Control-Allow-Credentials: true. - The server cannot use Access-Control-Allow-Origin: *; it must explicitly specify the requesting origin.