正在加载,请稍候…

How to Fix CORS Errors: A Step-by-Step Developer Guide

Understand why CORS errors happen and fix them properly — covering preflight requests, server-side headers, credentials, and common mistakes in Express, Nginx, and cloud APIs.

How to Fix CORS Errors

CORS is a browser security feature, not a server restriction. Your server receives the request fine — the browser blocks the response from reaching your JavaScript. CORS errors never happen with curl, Postman, or server-to-server calls.

The Same-Origin Policy prevents JavaScript on https://app.example.com from reading responses from https://api.other.com unless the API explicitly permits it via CORS headers.

Reading the Error

Chrome: Access to fetch at 'https://api.example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present.

Common variants:

  • "No Access-Control-Allow-Origin header" → server not sending CORS headers at all
  • "Value not equal to supplied origin" → wrong origin configured
  • "Response to preflight doesn't pass access control check" → OPTIONS request failing
  • "Allow-Credentials must be true" → credentials not enabled

Fix 1: Add CORS Headers on Your Server

Express / Node.js

const cors = require('cors');

// Allow specific origin (production)
app.use(cors({
  origin: 'https://app.example.com',
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,
  maxAge: 86400,
}));

// Multiple allowed origins
const allowedOrigins = ['https://app.example.com', 'http://localhost:3000'];
app.use(cors({
  origin: (origin, callback) => {
    if (!origin || allowedOrigins.includes(origin)) callback(null, true);
    else callback(new Error('Not allowed by CORS'));
  },
  credentials: true,
}));

Django

# pip install django-cors-headers
INSTALLED_APPS = [..., 'corsheaders']
MIDDLEWARE = ['corsheaders.middleware.CorsMiddleware', ...]
CORS_ALLOWED_ORIGINS = ['https://app.example.com']
CORS_ALLOW_CREDENTIALS = True

FastAPI

from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(CORSMiddleware,
  allow_origins=['https://app.example.com'],
  allow_credentials=True,
  allow_methods=['*'],
  allow_headers=['*'],
)

Nginx

location /api/ {
  if ($request_method = 'OPTIONS') {
    add_header 'Access-Control-Allow-Origin' 'https://app.example.com';
    add_header 'Access-Control-Allow-Methods' 'GET,POST,PUT,DELETE,OPTIONS';
    add_header 'Access-Control-Allow-Headers' 'Content-Type,Authorization';
    return 204;
  }
  add_header 'Access-Control-Allow-Origin' 'https://app.example.com' always;
  add_header 'Access-Control-Allow-Credentials' 'true' always;
}

Fix 2: Preflight Requests Failing

Complex requests (DELETE, PUT, custom headers like Authorization) trigger an OPTIONS preflight. If OPTIONS returns 404 or missing headers, the real request never happens.

// Express: handle OPTIONS before auth middleware
app.options('*', cors());
app.use(cors({ ... }));
app.use(authMiddleware);  // Auth comes AFTER cors

// Skip auth for OPTIONS preflight
function authMiddleware(req, res, next) {
  if (req.method === 'OPTIONS') return next();
  // verify token...
}

Fix 3: Credentials + Cookies

fetch(url, { credentials: 'include' });       // fetch
axios.get(url, { withCredentials: true });    // axios

Server must use specific origin, not wildcard:

# Wrong:  Access-Control-Allow-Origin: *  +  Access-Control-Allow-Credentials: true
# Right:  Access-Control-Allow-Origin: https://app.example.com
#         Access-Control-Allow-Credentials: true

Fix 4: APIs You Don't Control

Proxy through your own backend:

app.get('/api/proxy', async (req, res) => {
  const data = await fetch('https://third-party-api.com/data');
  res.json(await data.json());
});

Common Mistakes

  1. Duplicate CORS headers — Nginx and Express both adding headers; browser rejects duplicates
  2. CORS missing on error responses — 401/500 errors may miss the header, use the always flag
  3. Trailing slash mismatchhttps://app.com and https://app.com/ are different origins
  4. Not restarting the server — browser may also cache preflight results for Access-Control-Max-Age seconds

→ Check HTTP status codes at HTTP Status Codes.