Skip to main content
Cloud & AI Hub
Browse
Glossary AI Directory Playgrounds Models Prompts Explainers Strategy Matrix Benchmark Decoder
beginner 7 mins Read

Understanding CORS Header Preflight Requests

How browsers enforce the same-origin policy, why CORS preflight OPTIONS requests exist, and how to configure correct server-side CORS headers.

Introduction

CORS (Cross-Origin Resource Sharing) is a browser security mechanism that restricts web pages from making requests to a domain different from the one that served the page. When your frontend at https://app.example.com calls your API at https://api.example.com, the browser’s same-origin policy would normally block this. CORS is the protocol that allows servers to declare β€œI allow requests from this other origin” β€” without it, cross-origin APIs would be inaccessible from browsers (though not from curl or server-to-server calls).

Step-by-Step: How CORS Works

flowchart TD
    A["1. What Triggers CORS"]
    B["2. The Preflight Request"]
    C["3. Server Response (Correct CORS Headers)"]
    D["4. Express.js CORS Configuration"]
    E["5. Common CORS Mistakes"]
    A --> B
    B --> C
    C --> D
    D --> E

Step 1: What Triggers CORS

A cross-origin request occurs when any of these differ between page origin and request target: protocol (http vs https), domain (app.example.com vs api.example.com), or port (example.com:3000 vs example.com:8080).

Not all cross-origin requests are treated equally:

Simple requests (no preflight):
  - Methods: GET, POST, HEAD
  - Headers: only Content-Type (text/plain, multipart/form-data, application/x-www-form-urlencoded)
  
Non-simple requests (require preflight):
  - Methods: PUT, PATCH, DELETE, OPTIONS
  - Custom headers: Authorization, X-Api-Key, Content-Type: application/json
  - Most REST API calls with JSON bodies

Step 2: The Preflight Request

Before sending the actual request, the browser automatically sends an OPTIONS preflight to ask the server if the real request is allowed:

OPTIONS /api/users HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: Authorization, Content-Type

Step 3: Server Response (Correct CORS Headers)

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: Authorization, Content-Type, X-Api-Key
Access-Control-Max-Age: 86400
Access-Control-Allow-Credentials: true
  • Allow-Origin: Which origins are permitted (* = all, but cannot be used with credentials)
  • Allow-Methods: Which HTTP methods are allowed
  • Allow-Headers: Which request headers the browser may send
  • Max-Age: How long the browser can cache this preflight result (seconds)
  • Allow-Credentials: Whether cookies/auth headers are allowed (requires non-wildcard Allow-Origin)

Step 4: Express.js CORS Configuration

const cors = require('cors');

// Production CORS setup
const corsOptions = {
  origin: function (origin, callback) {
    const allowedOrigins = [
      'https://app.example.com',
      'https://admin.example.com',
      process.env.NODE_ENV === 'development' ? 'http://localhost:3000' : null
    ].filter(Boolean);
    
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
  allowedHeaders: ['Authorization', 'Content-Type', 'X-Api-Key'],
  credentials: true,
  maxAge: 86400  // 24 hours preflight cache
};

app.use(cors(corsOptions));
app.options('*', cors(corsOptions));  // Enable pre-flight for all routes

Step 5: Common CORS Mistakes

❌ Mistake: Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true
   Result: Browser blocks request β€” credentials require a specific origin
   Fix: Set specific origin instead of wildcard

❌ Mistake: Missing preflight handler (OPTIONS method not handled)
   Result: 405 Method Not Allowed on preflight β†’ main request never sent
   Fix: app.options('*', cors()) to handle all OPTIONS requests

❌ Mistake: CORS headers on error responses only
   Result: Browser sees the 401/403 error without CORS headers β†’ "Network Error" instead of error details
   Fix: Add CORS middleware BEFORE auth middleware so headers are present on all responses

❌ Mistake: Proxying through a same-origin path to bypass CORS
   Result: Works but hides the actual API URL from developers and adds latency
   Better: Configure CORS properly on the API server

Key Takeaways

  • CORS is a browser enforcement mechanism β€” server-to-server calls and curl are never subject to CORS restrictions.
  • Preflight OPTIONS requests are automatically sent by browsers before non-simple cross-origin requests β€” your server must handle them and return correct headers.
  • Access-Control-Allow-Origin: * cannot be combined with Access-Control-Allow-Credentials: true β€” credentials require a specific allowed origin.
  • Cache preflight responses with Access-Control-Max-Age to avoid OPTIONS requests on every API call.
  • CORS headers must be present on error responses too β€” place CORS middleware before authentication middleware.

Historical figures, architectures, and capabilities are for informational purposes only. Not technical, professional, legal, or financial advice. Sources: Research papers, developer documentation.