Back to Blog

The only article you ever need to read about CORS (2026 Guide)

CORS (Cross-Origin Resource Sharing) is the browser mechanism that controls cross-origin HTTP requests.

The only article you ever need to read about CORS (2026 Guide)

The essential facts about CORS

CORS (Cross-Origin Resource Sharing) is the browser mechanism that controls cross-origin HTTP requests. The Same-Origin Policy, introduced by Netscape Navigator 2.02 in 1995, blocks scripts from reading resources on different origins (protocol + host + port). CORS emerged as a standardized bypass mechanism, becoming a W3C Recommendation in January 2014 and now living in the WHATWG Fetch specification. Critically, CORS only prevents reading responses—cross-origin requests still execute, meaning CORS is not CSRF protection.


Technical fundamentals

The origin model

Two URLs share an origin only if all three components match: protocol (http:// vs https://), host (example.com vs api.example.com), and port (:80 vs :443). This means http://localhost:3000 and http://localhost:5000 are different origins—a common source of developer confusion.

Simple vs preflighted requests

A request is “simple” (no preflight) only if it uses GET, HEAD, or POST with only CORS-safelisted headers (Accept, Accept-Language, Content-Language, Content-Type with restrictions) and Content-Type limited to application/x-www-form-urlencoded, multipart/form-data, or text/plain. All JSON requests trigger preflight because application/json is not safelisted—this surprises many developers.

The preflight flow

  1. Browser sends OPTIONS with Origin, Access-Control-Request-Method, and Access-Control-Request-Headers
  2. Server must respond with Access-Control-Allow-Origin, Access-Control-Allow-Methods, and Access-Control-Allow-Headers
  3. Browser caches result per Access-Control-Max-Age (default: 5 seconds; Chrome max: 2 hours; Firefox max: 24 hours)
  4. Actual request proceeds only if preflight succeeds

All CORS headers explained

Response HeaderPurposeKey Behaviors
Access-Control-Allow-OriginAllowed originsSingle value only; use Vary: Origin with dynamic origins; * forbidden with credentials
Access-Control-Allow-MethodsAllowed HTTP methodsReturn in preflight response
Access-Control-Allow-HeadersAllowed request headersMust include all non-safelisted headers client sends
Access-Control-Allow-CredentialsEnables cookies/authOnly value is true; requires explicit origin, not *
Access-Control-Max-AgePreflight cache durationSeconds; reduces OPTIONS overhead
Access-Control-Expose-HeadersHeaders JS can readDefault: only Cache-Control, Content-Language, Content-Length, Content-Type, Expires, Last-Modified, Pragma
Request Header (set by browser)Purpose
Access-Control-Request-MethodMethod for actual request
Access-Control-Request-HeadersNon-safelisted headers to be used

Common CORS errors by browser

Chrome error patterns

Chrome uses consistent templates. The most frequent errors:

Access to fetch at '[URL]' from origin '[origin]' has been blocked by CORS policy: 
No 'Access-Control-Allow-Origin' header is present on the requested resource.
...Response to preflight request doesn't pass access control check: 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'.
...Request header field Authorization is not allowed by Access-Control-Allow-Headers 
in preflight response.

Chrome often masks underlying 4xx/5xx errors as CORS errors when Access-Control-Allow-Origin is missing from error responses.

Firefox error patterns

Firefox uses distinctive “Reason:” format with status codes:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote 
resource at [URL]. (Reason: CORS header 'Access-Control-Allow-Origin' missing). 
Status code: 404.

Firefox provides more verbose errors with specific reason codes like CORSMissingAllowOrigin, CORSNotSupportingCredentials, CORSPreflightDidNotSucceed, and links directly to MDN documentation.

Safari quirks

Safari has unique behaviors causing developer frustration:

  • Stricter about including protocol in allowed origins
  • WebKit disk cache can omit CORS headers on second load of static resources
  • May display CORS errors when underlying issue is different (e.g., 400 Bad Request)
  • Intelligent Tracking Prevention (ITP) affects credential scenarios

Real-world trigger scenarios

ScenarioCauseFix
Different portslocalhost:3000localhost:5000Configure server CORS
localhost vs 127.0.0.1Considered different originsUse consistent hostname
HTTP to HTTPSProtocol mismatchUse matching protocols
Custom headersAuthorization, Content-Type: application/json trigger preflightHandle OPTIONS request
Credentials with wildcard* + credentials blockedUse explicit origin
Subdomain requestsapp.example.comapi.example.comAllow specific subdomain

Server-side solutions by platform

Node.js/Express

// Using cors package (recommended)
const cors = require('cors');
app.use(cors({
  origin: ['https://example.com', 'https://app.example.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,
  maxAge: 86400
}));

// Dynamic origin validation
app.use(cors({
  origin: function(origin, callback) {
    const allowlist = ['https://example.com', 'https://app.example.com'];
    if (!origin || allowlist.includes(origin)) callback(null, true);
    else callback(new Error('Not allowed by CORS'));
  },
  credentials: true
}));

Python (Django, Flask, FastAPI)

# Django (django-cors-headers)
CORS_ALLOWED_ORIGINS = ["https://example.com"]
CORS_ALLOW_CREDENTIALS = True

# Flask (flask-cors)
CORS(app, resources={r"/api/*": {"origins": ["https://example.com"], "supports_credentials": True}})

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

Nginx

location /api/ {
    if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' 'https://example.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-Max-Age' 86400;
        return 204;
    }
    add_header 'Access-Control-Allow-Origin' 'https://example.com' always;
    add_header 'Access-Control-Allow-Credentials' 'true' always;
}

Apache (.htaccess)

<IfModule mod_headers.c>
    Header set Access-Control-Allow-Origin "https://example.com"
    Header set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
    Header set Access-Control-Allow-Headers "Content-Type, Authorization"
    Header set Access-Control-Allow-Credentials "true"
</IfModule>

AWS (S3, API Gateway, Lambda)

// S3 CORS configuration
[{
    "AllowedHeaders": ["*"],
    "AllowedMethods": ["GET", "PUT", "POST", "DELETE"],
    "AllowedOrigins": ["https://example.com"],
    "ExposeHeaders": ["ETag"],
    "MaxAgeSeconds": 86400
}]
// Lambda response
return {
    statusCode: 200,
    headers: {
        "Access-Control-Allow-Origin": "https://example.com",
        "Access-Control-Allow-Credentials": "true"
    },
    body: JSON.stringify(data)
};

Cloudflare Workers

export default {
  async fetch(request) {
    if (request.method === "OPTIONS") {
      return new Response(null, {
        status: 204,
        headers: {
          "Access-Control-Allow-Origin": "https://example.com",
          "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE",
          "Access-Control-Allow-Headers": "Content-Type, Authorization"
        }
      });
    }
    // Handle actual request...
  }
};

ASP.NET Core

builder.Services.AddCors(options => {
    options.AddPolicy("ApiPolicy", policy => {
        policy.WithOrigins("https://example.com")
              .AllowAnyMethod()
              .AllowAnyHeader()
              .AllowCredentials();
    });
});
app.UseCors("ApiPolicy");

Spring Boot

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("https://example.com")
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowCredentials(true)
                .maxAge(86400);
    }
}

Client-side workarounds when you can’t modify the server

Development-only options

Dev server proxies (recommended for local development):

// Vite
server: { proxy: { '/api': { target: 'http://localhost:4000', changeOrigin: true } } }

// Create React App (package.json)
"proxy": "http://localhost:4000"

// Next.js (next.config.js)
async rewrites() { return [{ source: '/api/:path*', destination: 'http://localhost:4000/:path*' }]; }

Browser extensions (CORS Unblock, Allow CORS) - development only, never browse normal websites with these enabled.

Chrome unsafe mode: chrome --disable-web-security --user-data-dir=/tmp/chrome_dev - never use for regular browsing.

CORS proxy services

ServiceStatusLimitations
corsproxy.ioActive1MB limit, text-only (HTML disabled), rate limiting
cors-anywhereSelf-host onlyPublic demo rate-limited since 2021
allorigins.winActiveURL prefix approach
cors.shActiveAlternative to cors-anywhere

Security warning: Third-party proxies can read all traffic. Never use for sensitive data, authentication, or production.

Production solutions

Backend-for-Frontend (BFF) pattern: Create a server-side proxy that:

  • Makes API calls from your server (no CORS)
  • Uses session cookies with browser (same-origin)
  • Keeps access tokens server-side (immune to XSS)

Serverless functions: AWS Lambda, Cloudflare Workers, or Vercel Edge Functions as proxies with your own CORS headers.


Advanced topics and security

Credentials and cookies

Using credentials: 'include' or withCredentials: true requires:

  • Server must return Access-Control-Allow-Credentials: true
  • Server must return explicit origin (not *) in Access-Control-Allow-Origin
  • Browser’s SameSite cookie settings may still block cookies (Chrome defaults to SameSite=Lax since 2020)

WebSockets bypass CORS entirely

WebSockets use WS/WSS protocols, not HTTP, so CORS does not apply. However:

  • Browser still sends Origin header in WebSocket handshake
  • Server must manually validate Origin or face Cross-Site WebSocket Hijacking (CSWSH)
  • Spring Framework defaults to same-origin only since 4.1.5

Security vulnerabilities to avoid

Most dangerous anti-pattern: Reflecting the Origin header without validation:

// DANGEROUS - allows any attacker site to read authenticated responses
res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');

Origin validation pitfalls:

  • Regex ^.*example\.com$ matches attackerexample.com
  • Suffix matching allows subdomain takeover attacks
  • Origin: null can be generated via sandboxed iframes, cross-origin redirects, or file: protocol

Missing Vary: Origin: Causes cache poisoning when dynamically setting CORS headers.

Private Network Access (CORS-RFC1918)

Chrome 94+ prevents public websites from accessing private network resources (localhost, 192.168.x.x). Requires:

  • Access-Control-Request-Private-Network: true in preflight
  • Access-Control-Allow-Private-Network: true in response
  • HTTPS secure context

SEO and content strategy

Competitive landscape

Top-ranking domains: MDN Web Docs (#1), PortSwigger Web Security Academy (#2), Wikipedia (#3), AWS docs (#4), Auth0 blog (#5). Stack Overflow has 14,600+ CORS questions.

Target keywords

CategoryKeywords
Primary”CORS error”, “what is CORS”, “Access-Control-Allow-Origin”
Long-tail errors”No ‘Access-Control-Allow-Origin’ header is present”, “preflight request doesn’t pass access control check”
Platform-specific”express cors”, “nginx cors”, “react cors error”, “AWS API Gateway CORS”
Scenario-based”CORS with credentials”, “disable CORS Chrome”, “CORS proxy”

Content gaps in existing articles

Existing content misses:

  • Visual troubleshooting flowcharts - decision trees for diagnosis
  • Error message mapping table - exact console error → specific fix
  • Environment separation - clear dev vs. production guidance
  • Security deep-dive - CORS misconfiguration exploitation
  • Multi-framework comparison - side-by-side code snippets
  • Interactive tools - CORS testers, error decoders

Recommended article structure

H1: The Complete Guide to CORS Errors
   
H2: What is CORS and why does it exist?
   H3: The Same-Origin Policy explained
   H3: How CORS relaxes these restrictions

H2: How CORS works technically
   H3: Simple requests vs preflighted requests
   H3: The preflight OPTIONS flow (diagram)
   H3: All CORS headers explained

H2: Common CORS errors and solutions
   H3: "No Access-Control-Allow-Origin header present"
   H3: "Preflight request doesn't pass access control check"
   H3: "Wildcard * not allowed with credentials"
   H3: Why Postman works but browsers don't

H2: Server-side configuration by platform (tabbed code)
   H3: Node.js/Express
   H3: Python (Django/Flask/FastAPI)
   H3: nginx and Apache
   H3: AWS (API Gateway, S3, Lambda)
   H3: .NET and Spring Boot

H2: Client-side workarounds
   H3: Dev server proxies (Vite, CRA, Next.js)
   H3: CORS proxy services and their risks
   H3: Browser extensions (dev only)

H2: CORS security best practices
   H3: Credential handling
   H3: Origin validation mistakes
   H3: WebSocket considerations

H2: Quick reference tables
   - Error → Solution mapping
   - Headers cheat sheet

Technical SEO recommendations

  • Target word count: 3,500–5,000 words
  • Schema markup: TechArticle, HowTo, FAQPage
  • Featured snippet optimization: 40–60 word definition for “What is CORS?”
  • Code presentation: Tabbed interface, copy buttons, syntax highlighting
  • Visuals: CORS flow diagram, preflight sequence diagram, error decision tree (4–6 total)
  • Internal linking: Related HTTP security topics
  • External links: MDN, WHATWG Fetch spec, RFC references

Quick reference tables

CORS headers cheat sheet

HeaderTypeRequired ForValue Example
Access-Control-Allow-OriginResponseAll CORShttps://example.com or *
Access-Control-Allow-MethodsResponsePreflightGET, POST, PUT, DELETE
Access-Control-Allow-HeadersResponsePreflight with custom headersContent-Type, Authorization
Access-Control-Allow-CredentialsResponseCookies/authtrue
Access-Control-Max-AgeResponseCaching preflight86400
Access-Control-Expose-HeadersResponseNon-safelisted response headersX-Custom-Header

Error → solution mapping

Error MessageLikely CauseFix
No ‘Access-Control-Allow-Origin’ headerServer not sending CORS headersAdd CORS configuration to server
Response to preflight doesn’t pass access control checkOPTIONS not handled or missing headersReturn CORS headers on OPTIONS with 204
Wildcard * not allowed with credentials mode ‘include’Using credentials: 'include' with *Use explicit origin
Request header field X not allowedMissing from Access-Control-Allow-HeadersAdd header to allowed list
Multiple Access-Control-Allow-Origin headersDuplicate CORS configurationConfigure CORS in only one layer

Preflight triggers

ConditionExampleTriggers Preflight?
GET with only Accept headerfetch('/api')No
POST with form-data<form enctype="multipart/form-data">No
POST with application/jsonContent-Type: application/jsonYes
Any custom headerAuthorization, X-API-KeyYes
PUT, DELETE, PATCH methodsfetch('/api', {method: 'DELETE'})Yes

Related blog posts

Create a free Account to fix CORS Errors in Production

Say goodbye to CORS errors and get back to building great web applications. It's free!

CORSPROXY Dashboard