正在加载,请稍候…

CORS Errors: What They Mean and How to Fix Them

Understand why CORS errors happen, what the browser console messages actually mean, and the correct server-side fixes for every scenario.

What Is CORS and Why Does It Exist?

CORS (Cross-Origin Resource Sharing) is a browser security mechanism that controls which cross-origin requests are allowed. A cross-origin request happens when JavaScript running on https://app.example.com tries to fetch data from https://api.other.com — a different origin (different domain, port, or protocol).

Without CORS, any website could silently read data from your bank, email, or any site you're logged into. CORS exists to prevent exactly this: it lets servers declare which origins they trust.

Importantly, CORS is enforced by the browser, not the server. The server still receives and processes the request — it just includes (or doesn't include) permission headers in the response. The browser then decides whether to expose that response to JavaScript.

The Most Common CORS Error Messages

"Access to XMLHttpRequest blocked by CORS policy: No 'Access-Control-Allow-Origin' header"

The server responded but didn't include the Access-Control-Allow-Origin header. The browser blocks the response from reaching your JavaScript.

Fix: Add the header on your server.

"Access to fetch blocked by CORS policy: The 'Access-Control-Allow-Origin' header has a value 'https://other.com' that is not equal to the supplied origin"

The server has CORS enabled, but it's only allowing a different origin than yours.

Fix: Ensure your origin is in the server's allowed origins list.

"CORS preflight response did not succeed"

For non-simple requests (PUT, DELETE, custom headers), the browser sends an OPTIONS preflight first. If that fails, the actual request never fires.

Fix: Handle OPTIONS requests explicitly on your server.

"Request header field Authorization is not allowed by Access-Control-Allow-Headers"

Your request includes a header the server hasn't explicitly allowed.

Fix: Add the header name to Access-Control-Allow-Headers.

What Makes a Request "Simple" vs "Preflighted"?

Simple requests (no preflight):

  • Method is GET, HEAD, or POST
  • Only uses safe headers: Accept, Accept-Language, Content-Language, Content-Type (with restrictions)
  • Content-Type is application/x-www-form-urlencoded, multipart/form-data, or text/plain

Preflighted requests (browser sends OPTIONS first):

  • Method is PUT, DELETE, PATCH, or any other method
  • Uses custom headers like Authorization, X-Custom-Header
  • Content-Type is application/json

This means most API calls from modern apps are preflighted because they use Content-Type: application/json or send Authorization headers.

Server-Side Fixes by Framework

Node.js / Express

const express = require('express');
const app = express();

// Option 1: Use the cors package (recommended)
const cors = require('cors');

app.use(cors({
  origin: ['https://myapp.com', 'https://staging.myapp.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,   // required if sending cookies/auth headers
  maxAge: 86400,       // cache preflight for 24 hours
}));

// Option 2: Manual headers
app.use((req, res, next) => {
  const allowedOrigins = ['https://myapp.com', 'https://staging.myapp.com'];
  const origin = req.headers.origin;
  if (allowedOrigins.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
  }
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  res.setHeader('Access-Control-Allow-Credentials', 'true');

  if (req.method === 'OPTIONS') {
    return res.sendStatus(204);  // preflight response
  }
  next();
});

Python / FastAPI

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://myapp.com", "https://staging.myapp.com"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

Python / Django

# settings.py
INSTALLED_APPS = [
    ...
    'corsheaders',
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',  # must be before CommonMiddleware
    'django.middleware.common.CommonMiddleware',
    ...
]

CORS_ALLOWED_ORIGINS = [
    "https://myapp.com",
    "https://staging.myapp.com",
]

CORS_ALLOW_CREDENTIALS = True

# Or for development (never in production):
# CORS_ALLOW_ALL_ORIGINS = True

Go / Gin

import "github.com/gin-contrib/cors"

r := gin.Default()
r.Use(cors.New(cors.Config{
    AllowOrigins:     []string{"https://myapp.com"},
    AllowMethods:     []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
    AllowHeaders:     []string{"Origin", "Content-Type", "Authorization"},
    AllowCredentials: true,
    MaxAge:           12 * time.Hour,
}))

Nginx (reverse proxy)

location /api/ {
    add_header 'Access-Control-Allow-Origin' 'https://myapp.com' always;
    add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
    add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always;
    add_header 'Access-Control-Allow-Credentials' 'true' always;

    if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Max-Age' 86400;
        add_header 'Content-Length' 0;
        return 204;
    }

    proxy_pass http://backend;
}

The credentials Trap

If your request includes cookies or an Authorization header, you must:

  1. Set Access-Control-Allow-Credentials: true on the server
  2. Set Access-Control-Allow-Origin to the specific origin — wildcards (*) don't work with credentials
  3. On the client: set credentials: 'include' in fetch, or withCredentials: true in Axios
// fetch
fetch('https://api.example.com/data', {
  credentials: 'include',  // send cookies
});

// Axios
axios.get('https://api.example.com/data', {
  withCredentials: true,
});

If you use Access-Control-Allow-Origin: * together with credentials, the browser will throw: "The value of the 'Access-Control-Allow-Origin' header must not be the wildcard '' when the request's credentials mode is 'include'."*

Development Workarounds (Not for Production)

Vite / webpack dev server proxy

// vite.config.js
export default {
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        rewrite: path => path.replace(/^/api/, ''),
      },
    },
  },
};

This proxies /api/* requests through Vite's server, making them same-origin to the browser — no CORS headers needed.

Browser extension (testing only)

Extensions like "CORS Unblock" disable CORS enforcement in the browser. Useful for quick testing. Never use in production, never ship code that depends on this.

Security: Never Use Access-Control-Allow-Origin: * on Authenticated APIs

A wildcard allows any origin to read your API response. For public, unauthenticated APIs (CDN assets, public data) this is fine. For any API that uses authentication or returns user-specific data, always specify exact allowed origins.

Debugging CORS Issues

  1. Open DevTools → Network tab → click the failing request → look at the Response Headers
  2. Check if Access-Control-Allow-Origin is present and matches your origin
  3. For preflighted requests, find the OPTIONS request that precedes the actual request and check its response
  4. Use curl to test the raw headers: curl -I -H "Origin: https://yourapp.com" https://api.example.com/endpoint

→ Use the URL Parser to break down API endpoints and inspect their components.