How to Fix CORS Errors — Complete Guide with Examples
What Is CORS and Why Does the Browser Enforce It?
CORS (Cross-Origin Resource Sharing) is a browser security mechanism that controls how web pages from one origin can request resources from a different origin. An origin is defined by the protocol (https), domain (api.example.com), and port (:443) — a change in any of these makes it a different origin.
Browsers enforce the Same-Origin Policy by default, which blocks JavaScript from making requests to a different origin than the page served from. CORS provides a way for servers to opt in and explicitly allow cross-origin requests via HTTP headers.
Why CORS Exists
Without CORS, any website could make authenticated requests to your bank's API, read your email, or access private data — all from within your browser session. CORS is the browser's way of asking the server: "Is this foreign website allowed to read your response?"
The critical distinction: CORS is enforced by the browser, not by the server. A server will still process a cross-origin request and return data — the browser just hides the response from the client-side JavaScript if the server did not include the appropriate CORS headers.
Common CORS Error Messages
"No 'Access-Control-Allow-Origin' header is present"
Access to fetch at 'https://api.example.com/data' from origin 'https://myapp.com'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present
on the requested resource.
Meaning: The server did not include the Access-Control-Allow-Origin header in its response. The browser received a response but cannot share it with your JavaScript.
"Response to preflight request doesn't pass access control check"
Access to fetch at 'https://api.example.com/submit' from origin 'https://myapp.com'
has been blocked by CORS policy: Response to preflight request doesn't pass
access control check: It does not have HTTP ok status.
Meaning: The browser sent an OPTIONS preflight request (described below) and the server returned an error status (like 404 or 500) instead of the expected 200 with CORS headers.
"Cannot set credentials with 'Access-Control-Allow-Origin' set to '*'"
Access to fetch at 'https://api.example.com/user' from origin 'https://myapp.com'
has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin'
header in the response must not be the wildcard '*' when the request's credentials
mode is 'include'.
Meaning: When sending cookies or authorization headers, the server must specify an explicit origin, not *. Wildcards are not allowed with credentials.
"Method PUT is not allowed by Access-Control-Allow-Methods"
Access to fetch at 'https://api.example.com/resource' from origin 'https://myapp.com'
has been blocked by CORS policy: Method PUT is not allowed by
Access-Control-Allow-Methods in preflight response.
Meaning: The preflight response listed allowed HTTP methods, but PUT (or DELETE, PATCH, etc.) was not included. The server must list all methods your frontend uses.
Server-Side Fix: Setting Access-Control-Allow-Origin
The core CORS fix is adding HTTP response headers on the server. Here is every header you need to understand:
Essential CORS Response Headers
| Header | Purpose | Example |
|---|---|---|
Access-Control-Allow-Origin |
Specifies which origins can read the response | https://myapp.com or * |
Access-Control-Allow-Methods |
Lists permitted HTTP methods | GET, POST, PUT, DELETE |
Access-Control-Allow-Headers |
Lists permitted request headers | Content-Type, Authorization |
Access-Control-Allow-Credentials |
Whether to expose cookies/auth headers | true |
Access-Control-Max-Age |
How long to cache the preflight result (seconds) | 86400 |
Access-Control-Expose-Headers |
Non-standard headers accessible to JS | X-Total-Count, X-Rate-Limit |
Basic Configuration
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
Important: Use a specific origin in production, not *. The wildcard is convenient for development but prevents credentials and is a security risk.
Framework-Specific Solutions
Express.js (Node.js)
The simplest approach uses the cors npm package:
const express = require('express');
const cors = require('cors');
const app = express();
// Allow all origins (development only)
app.use(cors());
// Restrict to specific origin (production)
app.use(cors({
origin: 'https://myapp.com',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 86400,
}));
// Dynamic origin based on request
const allowedOrigins = ['https://myapp.com', 'https://staging.myapp.com'];
app.use(cors({
origin: (origin, callback) => {
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
}));To handle OPTIONS preflight requests explicitly:
app.options('*', cors()); // Enable preflight for all routes
app.post('/api/data', cors(), (req, res) => {
res.json({ message: 'CORS-enabled POST request' });
});Nginx
Add CORS headers to your server block or location context:
server {
listen 443 ssl;
server_name api.example.com;
location / {
# Set CORS headers
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;
add_header 'Access-Control-Max-Age' '86400' always;
# Handle preflight requests
if ($request_method = 'OPTIONS') {
add_header 'Content-Length' '0';
add_header 'Content-Type' 'text/plain';
return 204;
}
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}The always flag ensures the header is sent even on error responses (4xx, 5xx).
Apache
Using .htaccess or the virtual host configuration:
<IfModule mod_headers.c>
Header always set Access-Control-Allow-Origin "https://myapp.com"
Header always set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
Header always set Access-Control-Allow-Headers "Content-Type, Authorization"
Header always set Access-Control-Allow-Credentials "true"
Header always set Access-Control-Max-Age "86400"
</IfModule>
# Handle preflight OPTIONS requests
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=204,L]Make sure mod_headers is enabled: sudo a2enmod headers.
Flask (Python)
Using the flask-cors extension:
from flask import Flask, jsonify
from flask_cors import CORS
app = Flask(__name__)
# Allow all origins (development)
CORS(app)
# Restrict to specific origin
CORS(app, resources={
r"/api/*": {
"origins": "https://myapp.com",
"methods": ["GET", "POST", "PUT", "DELETE"],
"allow_headers": ["Content-Type", "Authorization"],
"supports_credentials": True,
"max_age": 86400
}
})
@app.route('/api/data')
def get_data():
return jsonify({"message": "CORS-enabled response"})Django (Python)
Using django-cors-headers:
# settings.py
INSTALLED_APPS = [
...
'corsheaders',
...
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware', # As early as possible
'django.middleware.common.CommonMiddleware',
...
]
# Allow specific origin (production)
CORS_ALLOWED_ORIGINS = [
"https://myapp.com",
"https://staging.myapp.com",
]
# Or allow all (development only)
CORS_ALLOW_ALL_ORIGINS = True # Not for production!
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOW_METHODS = [
"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS",
]
CORS_ALLOW_HEADERS = [
"content-type", "authorization",
]
CORS_PREFLIGHT_MAX_AGE = 86400Handling Preflight OPTIONS Requests
When a browser sends a cross-origin request that is not a simple request, it first sends an OPTIONS preflight to check if the server allows the actual request.
What Triggers a Preflight?
A request is considered "simple" (no preflight needed) only when:
- Method is
GET,HEAD, orPOST - Content-Type is
application/x-www-form-urlencoded,multipart/form-data, ortext/plain - No custom headers like
AuthorizationorX-Custom-Header
Everything else triggers a preflight: PUT, DELETE, PATCH, application/json content type, custom headers, or credentials.
Preflight Flow
Browser Server
│ │
│ OPTIONS /api/data │
│ Origin: https://myapp.com │
│ Access-Control-Request-Method:│
│ POST │
│ Access-Control-Request-Headers:│
│ Content-Type, Authorization │
│───────────────────────────────>│
│ │
│ 200 OK │
│ Access-Control-Allow-Origin: │
│ https://myapp.com │
│ Access-Control-Allow-Methods: │
│ GET, POST, PUT │
│ Access-Control-Allow-Headers: │
│ Content-Type, Authorization │
│<───────────────────────────────│
│ │
│ POST /api/data │
│ Content-Type: application/json│
│ Authorization: Bearer ... │
│───────────────────────────────>│
│ │
│ 200 OK (with CORS headers) │
│<───────────────────────────────│
Your server must respond to OPTIONS with 200 (not 404, 405, or 500) and include the CORS headers. Many frameworks require explicit OPTIONS route handling.
Credentials and Cookies with CORS
When your frontend needs to send cookies, HTTP authentication, or client TLS certificates cross-origin:
Frontend (Fetch)
fetch('https://api.example.com/user', {
method: 'GET',
credentials: 'include', // Send cookies with the request
headers: {
'Content-Type': 'application/json',
},
});Frontend (Axios)
axios.get('https://api.example.com/user', {
withCredentials: true, // Send cookies
});Server Requirements
When credentials: 'include' is used:
Access-Control-Allow-Originmust be an explicit origin — The wildcard*is not allowed.Access-Control-Allow-Credentials: truemust be set — This header tells the browser to expose the response to the frontend.Access-Control-Allow-OriginandAccess-Control-Allow-Credentialsmust be consistent — The origin must match exactly; no pattern matching.
Important Security Note
With credentialed requests, the server should validate the Origin header against a whitelist in production. Never echo back the Origin header blindly:
// ❌ Dangerous — echoes any origin
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
next();
});
// ✅ Secure — validates against whitelist
const WHITELIST = ['https://myapp.com', 'https://admin.myapp.com'];
app.use((req, res, next) => {
const origin = req.headers.origin;
if (WHITELIST.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
}
next();
});Development Workarounds
Proxy Middleware (Webpack / Vite)
During local development, proxy API requests through your dev server to avoid CORS entirely:
Vite (vite.config.js):
export default {
server: {
proxy: {
'/api': {
target: 'https://api.example.com',
changeOrigin: true,
secure: true,
},
},
},
};Webpack Dev Server (webpack.config.js):
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'https://api.example.com',
changeOrigin: true,
secure: true,
},
},
},
};With a proxy, your frontend makes requests to /api/... on the same origin, so no CORS headers are needed.
CORS Browser Extensions
Extensions like "CORS Unblock" or "Allow CORS" disable CORS enforcement in the browser by intercepting response headers. These are useful for rapid prototyping, but never rely on them in production — they only affect your local browser, not your users'.
Disable CORS in Chrome (Development Only)
Launch Chrome with web security disabled:
# macOS
open -a Google Chrome --args --disable-web-security --user-data-dir=/tmp/chrome_dev
# Linux
google-chrome --disable-web-security --user-data-dir=/tmp/chrome_dev
# Windows
start chrome --disable-web-security --user-data-dir=C:/Temp/chrome_devWarning: This disables all same-origin policy checks. Never browse the web in this session — use it only for testing your own application.
Testing CORS with curl and REST Clients
Using curl
# Check if CORS headers are present
curl -H "Origin: https://myapp.com" \
-H "Access-Control-Request-Method: POST" \
-X OPTIONS \
-I https://api.example.com/data
# Expected response headers
# access-control-allow-origin: https://myapp.com
# access-control-allow-methods: GET, POST, PUT, DELETE
# access-control-allow-headers: Content-Type, Authorization
# access-control-max-age: 86400Simulating a Cross-Origin POST
curl -X POST https://api.example.com/data \
-H "Origin: https://myapp.com" \
-H "Content-Type: application/json" \
-d '{"key": "value"}' \
-IUsing LangStop REST API Client
The LangStop REST API Client lets you inspect response headers including CORS headers. Make a request and check the response headers tab for:
access-control-allow-originaccess-control-allow-methodsaccess-control-allow-headersaccess-control-allow-credentials
This is the fastest way to verify your CORS configuration is correct without writing any code.
Summary: CORS Fix Checklist
- Identify the origin — Determine which frontend origin is making the request.
- Add CORS headers — Set
Access-Control-Allow-Originto your frontend origin. - Handle preflight — Respond to
OPTIONSrequests with 200 and the appropriate CORS headers. - Allow methods and headers — List all HTTP methods and custom headers your frontend uses.
- Handle credentials — If sending cookies or auth, use
Access-Control-Allow-Credentials: trueand a specific origin. - Test with curl — Verify headers appear in the OPTIONS response.
- Use a proxy in development — Avoid CORS issues entirely by proxying API requests through your dev server.
Related LangStop Tools
- REST API Client — Test APIs and inspect CORS headers
- URL Encoder — Encode query parameters for API requests
- JWT Encoder / Decoder — Inspect and debug JWT tokens
- JSON Formatter — Pretty-print API responses
- JSON Validator — Validate JSON payloads
- Base64 Encoder — Encode binary data for API calls