---
# === IDENTITY ===
id: software/security/cors-configuration/2026
canonical_question: "How do I configure CORS correctly?"
aliases:
  - "CORS configuration best practices"
  - "cross-origin resource sharing setup"
  - "Access-Control-Allow-Origin configuration"
  - "fix CORS errors"
  - "CORS preflight request setup"
  - "enable CORS in Express Django Go Spring Boot"
  - "CORS security headers"
  - "how to allow cross-origin requests"
entity_type: software_reference
domain: software > security > CORS Configuration
region: global
jurisdiction: global
temporal_scope: 2024-2026

# === VERIFICATION ===
last_verified: 2026-02-27
confidence: 0.94
version: 1.0
first_published: 2026-02-27

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "Fetch specification replaced the standalone CORS spec (2014); Private Network Access (PNA) preflight requirements added to Chrome 104+ (2022)"
  next_review: 2026-08-26
  change_sensitivity: low

# === CONSTRAINTS ===
constraints:
  - "NEVER use Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true -- browsers reject this combination"
  - "NEVER reflect the Origin header directly into Access-Control-Allow-Origin without validating against an allowlist"
  - "NEVER whitelist the null origin -- sandboxed iframes and data: URIs can forge null origins"
  - "Preflight responses (OPTIONS) MUST return 200/204 with the correct CORS headers or the actual request will be blocked"
  - "CORS is enforced by the browser only -- server-side callers bypass CORS entirely, so CORS is NOT a substitute for authentication/authorization"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Need to prevent XSS attacks, not configure cross-origin access"
    use_instead: "software/security/xss-prevention/2026"
  - condition: "Need CSRF protection (token-based, not header-based)"
    use_instead: "software/security/csrf-prevention/2026"
  - condition: "Need to configure Content Security Policy (CSP) frame-ancestors or connect-src"
    use_instead: "software/security/csp-configuration/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: "framework"
    question: "What web framework or language is your server built with?"
    type: choice
    options: ["Express/Node.js", "Django/Python", "Go (net/http)", "Spring Boot/Java", "ASP.NET Core", "Nginx/Apache (reverse proxy)", "Other"]
  - key: "credential_mode"
    question: "Do cross-origin requests need to send cookies or Authorization headers?"
    type: choice
    options: ["Yes (credentialed requests)", "No (public/anonymous access)"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/security/cors-configuration/2026"
suggested_citation: "Source: knowledgelib.io -- AI Knowledge Library (verified 2026-02-27)"

# === RELATED UNITS ===
related_kos:
  depends_on: []
  related_to:
    - id: "software/security/xss-prevention/2026"
      label: "XSS Prevention Guide"
    - id: "software/security/csp-configuration/2026"
      label: "Content Security Policy Configuration"
  solves: []
  alternative_to: []
  often_confused_with:
    - id: "software/security/csrf-prevention/2026"
      label: "CSRF Prevention"
    - id: "software/security/csp-configuration/2026"
      label: "CSP Configuration"

# === SOURCES (7 authoritative sources) ===
# Types: official_docs, technical_blog, rfc_spec, academic_paper, community_resource, industry_report
# Reliability: high, moderate_high, moderate, moderate_low, low, authoritative
sources:
  - id: src1
    title: "Cross-Origin Resource Sharing (CORS) - HTTP"
    author: MDN Web Docs
    url: https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS
    type: official_docs
    published: 2025-10-01
    reliability: authoritative
  - id: src2
    title: "Cross-Origin Resource Sharing (CORS) Configuration"
    author: MDN Web Docs
    url: https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/CORS
    type: official_docs
    published: 2025-10-01
    reliability: authoritative
  - id: src3
    title: "What is CORS (cross-origin resource sharing)?"
    author: PortSwigger
    url: https://portswigger.net/web-security/cors
    type: technical_blog
    published: 2025-03-01
    reliability: high
  - id: src4
    title: "Fetch Standard (CORS specification)"
    author: WHATWG
    url: https://fetch.spec.whatwg.org/
    type: rfc_spec
    published: 2025-12-01
    reliability: authoritative
  - id: src5
    title: "CORS for Developers"
    author: W3C Web Application Security Working Group
    url: https://w3c.github.io/webappsec-cors-for-developers/
    type: rfc_spec
    published: 2024-06-01
    reliability: authoritative
  - id: src6
    title: "Express cors middleware"
    author: Express.js
    url: https://expressjs.com/en/resources/middleware/cors.html
    type: official_docs
    published: 2025-01-01
    reliability: high
  - id: src7
    title: "WSTG - Testing Cross Origin Resource Sharing"
    author: OWASP Foundation
    url: https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/11-Client-side_Testing/07-Testing_Cross_Origin_Resource_Sharing
    type: community_resource
    published: 2024-09-01
    reliability: authoritative
---

# CORS Configuration: Cross-Origin Resource Sharing Guide

## TL;DR

- **Bottom line**: Configure CORS by setting explicit `Access-Control-Allow-Origin` values from a validated allowlist -- never reflect the `Origin` header blindly or use `*` with credentials.
- **Key tool/command**: `Access-Control-Allow-Origin: https://your-frontend.com` with `Vary: Origin` on every CORS response.
- **Watch out for**: Using `Access-Control-Allow-Origin: *` together with `Access-Control-Allow-Credentials: true` -- browsers silently reject this, and reflecting any Origin header creates a universal CORS bypass vulnerability.
- **Works with**: All modern browsers (Chrome 4+, Firefox 3.5+, Safari 4+, Edge 12+). CORS is defined by the WHATWG Fetch specification, not a standalone W3C spec.

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- NEVER use `Access-Control-Allow-Origin: *` with `Access-Control-Allow-Credentials: true` -- browsers reject this combination [src1]
- NEVER reflect the `Origin` header directly into `Access-Control-Allow-Origin` without validating against an allowlist [src3]
- NEVER whitelist the `null` origin -- sandboxed iframes and `data:` URIs can forge null origins [src3]
- Preflight responses (OPTIONS) MUST return 200/204 with the correct CORS headers or the actual request will be blocked [src4]
- CORS is enforced by the browser only -- server-side callers bypass CORS entirely, so CORS is NOT a substitute for authentication/authorization [src5]

## Quick Reference

| Header | Purpose | Values | Security Risk if Wrong |
|---|---|---|---|
| `Access-Control-Allow-Origin` | Specifies which origin(s) may read the response | Exact origin (`https://app.example.com`), `*` (public), or omit | Reflecting any origin = universal CORS bypass [src3] |
| `Access-Control-Allow-Methods` | Allowed HTTP methods for the actual request | `GET, POST, PUT, DELETE` (only what you need) | Overly permissive methods expand attack surface |
| `Access-Control-Allow-Headers` | Custom headers the client may send | `Content-Type, Authorization` (only what you need) | Allowing `*` with credentials is rejected by browsers [src1] |
| `Access-Control-Allow-Credentials` | Permits cookies/auth headers in cross-origin requests | `true` or omit (never `false`) | Must pair with an explicit origin, never `*` [src2] |
| `Access-Control-Expose-Headers` | Response headers the client JS can read | `X-Request-Id, X-Total-Count` | Omitting hides custom response headers from `fetch()` |
| `Access-Control-Max-Age` | Seconds to cache preflight results | `600` (10 min) to `86400` (24h) | Too low = excessive preflight requests; too high = stale policy |
| `Vary: Origin` | Tells caches the response differs by Origin | Always include when ACAO is not `*` | Omitting causes caches to serve wrong origin's CORS headers [src1] |
| `Access-Control-Request-Method` | (Request) Method the browser plans to use | Sent automatically by browser in preflight | N/A (read-only) |
| `Access-Control-Request-Headers` | (Request) Custom headers the browser plans to send | Sent automatically by browser in preflight | N/A (read-only) |

## Decision Tree

```
START: Does your API need cross-origin access?
|-- NO --> Do not set any CORS headers (same-origin policy protects you)
|-- YES |
        |-- Does the request carry cookies or Authorization headers?
        |   |-- YES (credentialed) |
        |   |   |-- Set Access-Control-Allow-Origin to the EXACT requesting origin (from allowlist)
        |   |   |-- Set Access-Control-Allow-Credentials: true
        |   |   |-- Set Vary: Origin
        |   |   |-- NEVER use * for origin
        |   |-- NO (public/anonymous) |
        |       |-- Is the API fully public (CDN, open data)?
        |       |   |-- YES --> Access-Control-Allow-Origin: *
        |       |   |-- NO --> Set specific origin(s) from allowlist
        |
        |-- Does the request use non-simple methods (PUT, DELETE, PATCH)?
        |   |-- YES --> Handle OPTIONS preflight: return 200/204 with Allow-Methods, Allow-Headers, Max-Age
        |   |-- NO (GET, HEAD, POST with simple headers) --> No preflight needed, set ACAO on response
        |
        |-- Multiple frontends need access?
            |-- YES --> Validate Origin against allowlist at runtime, set ACAO dynamically + Vary: Origin
            |-- NO --> Hardcode the single allowed origin
```

## Step-by-Step Guide

### 1. Identify your cross-origin requirements

Determine which origins need access, whether requests carry credentials (cookies, Authorization headers), and which HTTP methods and custom headers are needed. [src2]

```
Questions to answer:
- Frontend origin(s): https://app.example.com, https://staging.example.com
- Credentials needed? Yes (session cookies) / No (public API)
- Methods needed: GET, POST, PUT, DELETE
- Custom headers: Authorization, Content-Type, X-Request-Id
```

**Verify**: Check browser DevTools > Network tab for blocked CORS requests (they appear as errors in the Console).

### 2. Implement origin validation on the server

Never reflect the Origin header blindly. Validate it against an allowlist and set the appropriate response headers. [src3]

```javascript
// Origin validation pattern (pseudocode for any framework)
const ALLOWED_ORIGINS = new Set([
  'https://app.example.com',
  'https://staging.example.com'
]);

function getCorsOrigin(requestOrigin) {
  if (ALLOWED_ORIGINS.has(requestOrigin)) {
    return requestOrigin;  // Echo back the validated origin
  }
  return null;  // Do not set ACAO header
}
```

**Verify**: `curl -H "Origin: https://evil.com" -I https://your-api.com/data` -- should NOT return `Access-Control-Allow-Origin: https://evil.com`

### 3. Handle preflight (OPTIONS) requests

Browsers send a preflight OPTIONS request before any "non-simple" cross-origin request (PUT, DELETE, PATCH, or requests with custom headers). Your server must respond to OPTIONS with the correct CORS headers. [src1]

```http
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: 600
Vary: Origin
```

**Verify**: `curl -X OPTIONS -H "Origin: https://app.example.com" -H "Access-Control-Request-Method: PUT" -I https://your-api.com/data` -- should return 200/204 with CORS headers.

### 4. Set headers on actual responses

Every cross-origin response (not just preflight) must include the `Access-Control-Allow-Origin` header. [src1]

```http
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: X-Request-Id
Vary: Origin
Content-Type: application/json
```

**Verify**: In browser DevTools > Network, click on a cross-origin request and confirm the response includes `Access-Control-Allow-Origin` matching the requesting origin.

### 5. Test with real browsers and curl

Automated tests should verify CORS headers for allowed origins, blocked origins, and preflight behavior. [src7]

```bash
# Test allowed origin
curl -sI -H "Origin: https://app.example.com" https://your-api.com/data \
  | grep -i access-control

# Test blocked origin (should NOT return ACAO)
curl -sI -H "Origin: https://evil.com" https://your-api.com/data \
  | grep -i access-control

# Test preflight
curl -sI -X OPTIONS \
  -H "Origin: https://app.example.com" \
  -H "Access-Control-Request-Method: DELETE" \
  -H "Access-Control-Request-Headers: Authorization" \
  https://your-api.com/data | grep -i access-control
```

**Verify**: Allowed origin returns correct ACAO; blocked origin returns no ACAO header; preflight returns Allow-Methods and Allow-Headers.

## Code Examples

### Express.js (Node.js): Secure CORS with allowlist

> Full script: [express-js-node-js-secure-cors-with-allowlist.js](scripts/express-js-node-js-secure-cors-with-allowlist.js) (28 lines)

```javascript
// Input:  Express app needing cross-origin access from specific frontends
// Output: CORS headers on all responses, validated against allowlist
const express = require('express');  // ^4.18.0
const cors = require('cors');        // ^2.8.5
const app = express();
# ... (see full script)
```

### Django (Python): django-cors-headers configuration

> Full script: [django-python-django-cors-headers-configuration.py](scripts/django-python-django-cors-headers-configuration.py) (26 lines)

```python
# Input:  Django project needing CORS for a separate frontend
# Output: CORS middleware adds headers to all responses
# 1. Install: pip install django-cors-headers
# 2. settings.py:
INSTALLED_APPS = [
# ... (see full script)
```

### Go (net/http): rs/cors middleware

```go
// Input:  Go HTTP server needing CORS with origin validation
// Output: CORS headers on all cross-origin responses

package main

import (
    "net/http"
    "github.com/rs/cors"  // v1.11+
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/api/data", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        w.Write([]byte(`{"message":"CORS-enabled response"}`))
    })

    c := cors.New(cors.Options{
        AllowedOrigins:   []string{"https://app.example.com", "https://staging.example.com"},
        AllowedMethods:   []string{"GET", "POST", "PUT", "DELETE"},
        AllowedHeaders:   []string{"Content-Type", "Authorization"},
        ExposedHeaders:   []string{"X-Request-Id"},
        AllowCredentials: true,
        MaxAge:           600,   // Cache preflight for 10 minutes
    })

    handler := c.Handler(mux)
    http.ListenAndServe(":8080", handler)
}
```

### Spring Boot (Java): Global CORS configuration

> Full script: [spring-boot-java-global-cors-configuration.java](scripts/spring-boot-java-global-cors-configuration.java) (29 lines)

```java
// Input:  Spring Boot app needing CORS for specific frontend origins
// Output: CORS headers on all /api/** endpoints
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
# ... (see full script)
```

## Anti-Patterns

### Wrong: Reflecting the Origin header without validation

```javascript
// BAD -- reflects any origin, creating a universal CORS bypass
app.use((req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
  res.setHeader('Access-Control-Allow-Credentials', 'true');
  next();
});
// Any website can now read authenticated responses from your API
```

### Correct: Validate Origin against an allowlist

```javascript
// GOOD -- only allows known origins
const ALLOWED = new Set(['https://app.example.com']);
app.use((req, res, next) => {
  const origin = req.headers.origin;
  if (ALLOWED.has(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
    res.setHeader('Access-Control-Allow-Credentials', 'true');
    res.setHeader('Vary', 'Origin');
  }
  next();
});
```

### Wrong: Wildcard origin with credentials

```javascript
// BAD -- browsers reject * with credentials, and this fails silently
app.use(cors({
  origin: '*',
  credentials: true
}));
// Fetch requests with credentials: 'include' will be blocked
```

### Correct: Explicit origin with credentials

```javascript
// GOOD -- specific origin allows credentialed requests
app.use(cors({
  origin: 'https://app.example.com',
  credentials: true
}));
```

### Wrong: Allowing the null origin

```http
# BAD -- null origin can be forged by sandboxed iframes
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
```

### Correct: Only allow real HTTPS origins

```http
# GOOD -- explicit HTTPS origin only
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Vary: Origin
```

### Wrong: Missing Vary: Origin header

```javascript
// BAD -- CDN/proxy caches the ACAO for one origin, serves it to all
app.use((req, res, next) => {
  const origin = req.headers.origin;
  if (allowed.has(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
    // Missing: Vary: Origin
  }
  next();
});
```

### Correct: Always include Vary: Origin

```javascript
// GOOD -- tells caches that response varies by Origin header
app.use((req, res, next) => {
  const origin = req.headers.origin;
  if (allowed.has(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
    res.setHeader('Vary', 'Origin');
  }
  next();
});
```

### Wrong: Regex-based origin matching without anchoring

```javascript
// BAD -- matches evil-example.com or example.com.evil.com
const isAllowed = /example\.com/.test(origin);
```

### Correct: Exact match or properly anchored regex

```javascript
// GOOD -- exact match from Set (preferred)
const ALLOWED = new Set(['https://app.example.com']);
const isAllowed = ALLOWED.has(origin);

// GOOD -- if regex needed, anchor both ends
const isAllowed = /^https:\/\/[\w-]+\.example\.com$/.test(origin);
```

## Common Pitfalls

- **Missing Vary: Origin**: When dynamically setting `Access-Control-Allow-Origin` based on the request Origin, omitting `Vary: Origin` causes CDNs and browser caches to serve the wrong origin's CORS policy to subsequent requests. Fix: Always set `Vary: Origin` when ACAO is not `*`. [src1]
- **Preflight not handled**: Server returns 404/405 for OPTIONS requests, causing all non-simple cross-origin requests to fail. Fix: Ensure your framework handles OPTIONS routes; most CORS middleware does this automatically. [src4]
- **CORS middleware ordering**: In Express and Django, CORS middleware must run before route handlers. If placed after, responses are sent without CORS headers. Fix: In Express, call `app.use(cors(...))` before route definitions. In Django, place `CorsMiddleware` before `CommonMiddleware`. [src6]
- **Subdomain matching pitfalls**: Using string `.endsWith('example.com')` to validate origins also matches `evilexample.com`. Fix: Parse origins as URLs and compare the hostname exactly, or use a Set of allowed origins. [src3]
- **Forgetting exposed headers**: By default, browsers only expose simple response headers (Cache-Control, Content-Language, Content-Type, etc.) to JS. Custom headers like `X-Request-Id` are hidden. Fix: Add them to `Access-Control-Expose-Headers`. [src1]
- **Wildcard in Access-Control-Allow-Headers with credentials**: Browsers reject `Access-Control-Allow-Headers: *` when credentials mode is `include`. Fix: List specific headers explicitly. [src4]
- **HTTP origins trusted from HTTPS page**: Allowing `http://app.example.com` as a CORS origin from an HTTPS API enables man-in-the-middle attacks to inject CORS requests. Fix: Only trust HTTPS origins. [src3]

## Diagnostic Commands

> Full script: [diagnostic-commands.sh](scripts/diagnostic-commands.sh) (25 lines)

```bash
# Check CORS headers for a specific origin
curl -sI -H "Origin: https://app.example.com" https://your-api.com/endpoint \
  | grep -i "access-control\|vary"
# Test preflight (OPTIONS) request
curl -sI -X OPTIONS \
# ... (see full script)
```

## Version History & Compatibility

| Standard/Feature | Status | Browser Support | Key Change |
|---|---|---|---|
| CORS (Fetch spec) | Living Standard | All modern browsers | Core CORS mechanism: preflight, ACAO, credentials [src4] |
| Private Network Access (PNA) | Draft | Chrome 104+ | Preflight required for requests from public to private networks |
| `Access-Control-Allow-Private-Network` | Draft | Chrome 104+ | New header for PNA preflight responses |
| Wildcard `*` in ACAH/ACAM | Fetch spec | Chrome 63+, Firefox 69+, Safari 15.4+ | Wildcard allowed for non-credentialed requests only |
| `Access-Control-Max-Age` default | Fetch spec | Varies | Chrome: 5s default, Firefox: 24h, Safari: 5s [src1] |
| Preflight caching (`Max-Age`) | Fetch spec | All modern | Chrome cap: 7200s (2h), Firefox: 86400s (24h) |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Frontend and API are on different origins (e.g., `app.com` + `api.com`) | Frontend and API share the same origin (scheme + host + port) | No CORS needed -- same-origin policy allows the request |
| Third-party integrations need to call your public API | Protecting sensitive data from cross-origin access | Authentication/authorization on the server side |
| Mobile app or desktop client uses a web view hitting your API | Server-to-server communication (no browser involved) | CORS is irrelevant -- use API keys/mTLS instead |
| CDN serves fonts/images to pages on different origins | You want to restrict which servers can call your API | CORS does NOT restrict non-browser clients; use auth |

## Important Caveats

- CORS is a browser-enforced mechanism only -- any non-browser HTTP client (curl, Postman, server-side code) completely ignores CORS headers, so never rely on CORS as your sole access control [src5]
- The `Origin` header can be spoofed by non-browser clients; always pair CORS with proper authentication and authorization on every endpoint [src7]
- `Access-Control-Max-Age` has different maximum caps across browsers: Chrome caps at 7200 seconds (2 hours), Firefox at 86400 seconds (24 hours) -- setting higher values is silently clamped [src1]
- Private Network Access (PNA) is a new Chrome-only feature (as of 2026) that requires preflight for requests from public networks to private/local networks -- this may break existing localhost development setups
- When deploying behind a CDN or reverse proxy, ensure the proxy forwards the `Origin` header and does not strip or cache CORS response headers inappropriately -- always use `Vary: Origin` [src2]
- The `null` origin is a valid value sent by sandboxed iframes, `data:` URIs, and local file:// pages -- never allow it in your allowlist as it can be forged [src3]

## Related Units
<!-- Generated from related_kos frontmatter -->

- [XSS Prevention Guide](/software/security/xss-prevention/2026)
- [Content Security Policy Configuration](/software/security/csp-configuration/2026)
- [CSRF Prevention](/software/security/csrf-prevention/2026)
