Developer

How to Fix CORS Errors: Access-Control-Allow-Origin, Preflight OPTIONS, and Headers Explained

Solve CORS blocking in web applications. Learn how Access-Control-Allow-Origin works, how to handle OPTIONS preflight requests, and how to debug headers locally.

The Synctoolo Team··10 min read
Server rack illuminated with fiber optic network cables in a secure modern datacenter

Every web developer encounters the dreaded browser console error: "Access to fetch at 'https://api.example.com/data' from origin 'https://mywebsite.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource."

When this happens, developers often spend hours guessing header configurations, restarting servers, or downloading questionable browser extensions to disable security settings. Worse, many resort to adding Access-Control-Allow-Origin: * in production, inadvertently exposing private internal endpoints to malicious cross-site scripting attacks.

This guide breaks down exactly why browsers enforce Cross-Origin Resource Sharing (CORS), how HTTP preflight requests work under the hood, and how to configure robust CORS policies in modern backend services. When testing or verifying raw API response headers and authentication payloads, you can inspect and validate them privately using Synctoolo's client-side JSON Formatter and Base64 Encoder / Decoder.

What Is CORS and Why Does the Browser Block Requests?

CORS is not a server error; it is a security mechanism enforced strictly by web browsers. It exists to protect users by extending the Same-Origin Policy (SOP).

Under the Same-Origin Policy, a web page loaded from one origin can only make network requests to the exact same origin. An origin is defined by the three-part tuple: protocol + domain + port:

Compared URL Outcome vs https://app.example.com:443 Reason
https://app.example.com/dashboard Same Origin Protocol, domain, and port match exactly.
http://app.example.com/dashboard Blocked (Cross-Origin) Different protocol (HTTP vs HTTPS).
https://api.example.com/data Blocked (Cross-Origin) Different subdomain (api vs app).
https://app.example.com:8080/data Blocked (Cross-Origin) Different port (8080 vs 443).

Without CORS, if you visited a malicious website while logged into your online bank, scripts on that site could silently send authenticated POST requests to your bank's API using your browser session cookies. CORS gives the receiving API server the power to declare explicitly which foreign origins are permitted to read its responses.

High-density network switch ports connecting cloud microservices
Cross-Origin Resource Sharing (CORS) enforces browser security boundaries when frontend applications communicate across separate API origins. Photo by Thomas Jensen on Unsplash.

Simple Requests vs Preflighted OPTIONS Requests

Browsers divide cross-origin requests into two categories: Simple Requests and Preflighted Requests.

1. Simple Requests

A request is considered "simple" if it uses the HTTP method GET, HEAD, or POST, and only includes basic headers defined by the browser (such as Accept, Accept-Language, or Content-Type set to application/x-www-form-urlencoded, multipart/form-data, or text/plain).

For simple requests, the browser sends the HTTP request directly, appending an Origin: https://mywebsite.com header. The server processes the request and returns a response. If the response contains Access-Control-Allow-Origin: https://mywebsite.com, the browser exposes the data to your JavaScript. If the header is missing, the browser throws a CORS error and discards the response.

2. Preflighted Requests

If your request sends custom headers (such as Authorization: Bearer <token>) or uses a Content-Type of application/json, the browser considers it complex. Before sending the actual request, the browser automatically dispatches an exploratory HTTP OPTIONS preflight request to verify server permissions.

The preflight request contains headers describing the pending operation:

OPTIONS /api/user/profile HTTP/1.1
Host: api.example.com
Origin: https://mywebsite.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization, content-type

Your server must respond to this OPTIONS probe with an HTTP 200 or 204 status and valid permission headers. If the preflight fails, the actual request is never sent.

The 5 Core CORS Response Headers

To satisfy the browser, your API server must return these response headers:

  1. Access-Control-Allow-Origin: The exact requesting domain allowed to access the resource (e.g. https://mywebsite.com) or * for public data.
  2. Access-Control-Allow-Methods: Comma-separated list of permitted HTTP verbs (e.g. GET, POST, PUT, DELETE, OPTIONS).
  3. Access-Control-Allow-Headers: Comma-separated list of allowed incoming headers (e.g. Content-Type, Authorization).
  4. Access-Control-Allow-Credentials: Set to true if your frontend sends cookies, HTTP Basic authentication, or TLS client certificates.
  5. Access-Control-Max-Age: The duration in seconds (e.g. 86400 for 24 hours) the browser can cache the preflight result without sending another OPTIONS request.

How to Correctly Configure CORS in Common Backends

Express.js (Node.js)

Using the official cors middleware, specify explicit whitelists rather than opening wildcards:

import express from 'express';
import cors from 'cors';

const app = express();
const allowedOrigins = ['https://mywebsite.com', 'https://staging.mywebsite.com'];

app.use(cors({
  origin: (origin, callback) => {
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Blocked by CORS policy'));
    }
  },
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization']
}));

Next.js Route Handlers (App Router)

In Next.js, you can handle preflight requests directly within your route files by exporting an OPTIONS handler:

export async function OPTIONS() {
  return new Response(null, {
    status: 204,
    headers: {
      'Access-Control-Allow-Origin': 'https://mywebsite.com',
      'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type, Authorization',
      'Access-Control-Max-Age': '86400',
    },
  });
}

The Credentials Wildcard Trap

One of the most frequent CORS bugs happens when combining cookies with wildcard origins. The W3C specification strictly dictates:

If a request includes credentials (cookies or Authorization headers), the server CANNOT use a wildcard (*) for Access-Control-Allow-Origin. It must return the exact matching origin string.

If your frontend sets credentials: 'include' in fetch(), returning Access-Control-Allow-Origin: * will cause an immediate browser rejection. Always echo back the verified origin from your incoming request whitelist.

Tools mentioned in this article

FAQ

Why does CORS only happen in browsers and not in Postman or curl?+

CORS is a browser-enforced security policy created to protect everyday users from cross-site request forgery. Server-side tools like Postman, curl, and backend microservices do not enforce the Same-Origin Policy and execute direct TCP requests without browser restrictions.

Does an HTTP 500 error cause a CORS error?+

Yes, frequently. When a backend server crashes or returns an unhandled 500 Internal Server Error, the crash response usually skips your CORS middleware. As a result, the response lacks the Access-Control-Allow-Origin header, causing the browser to report a CORS failure instead of the underlying backend error.

How do I handle CORS in local development between port 3000 and 8080?+

In local development, configure your frontend build tool (such as Next.js rewrites or Vite proxy) to proxy requests to your backend server. This ensures all requests originate from the same host and port, bypassing browser CORS restrictions entirely without disabling security headers.

Is Access-Control-Allow-Origin: * safe for public APIs?+

Yes. If your API serves public, unauthenticated data that requires no cookies, authorization headers, or user-specific payloads (such as public exchange rates or public weather statistics), using a wildcard is standard and safe practice.

S
The Synctoolo Team

We build and review free, privacy-first tools at Synctoolo.

Keep reading