Handling CORS

1. Understanding Cross-Origin Requests

Browsers block JS from reading responses across origins (different scheme/host/port) unless server opts in via CORS headers.

Same OriginCross Origin
https://app.comhttps://app.com/apihttps://app.comhttps://api.com
No CORS neededServer must send CORS headers

2. Configuring Access-Control-Allow-Origin

ValueEffect
*Any origin (no credentials allowed)
https://app.example.comSingle specific origin
Echo request Origin (validated)Multi-origin support; must validate against allowlist
nullAvoid — file:// and sandboxed iframes

3. Configuring Access-Control-Allow-Methods

HeaderValue
Allow common methodsGET, POST, PUT, PATCH, DELETE, OPTIONS
Per-endpoint specificOnly methods that endpoint supports
RequiredFor preflight responses

4. Configuring Access-Control-Allow-Headers

HeaderNotes
List custom headersContent-Type, Authorization, X-Request-ID
Echo requestReflect Access-Control-Request-Headers (validated)
Wildcard *Allowed (without credentials)

5. Handling Preflight Requests

CORS Preflight Flow

  1. Browser detects "non-simple" request (custom header, PUT/DELETE, JSON body, etc.)
  2. Sends OPTIONS request with Access-Control-Request-Method + ...-Headers
  3. Server responds with Access-Control-Allow-* headers (no body, 204)
  4. Browser caches preflight per Access-Control-Max-Age
  5. Browser sends actual request

6. Using Access-Control-Max-Age

ValueEffect
600 (10 min)Common default
86400 (24h)Max in Chrome (effective cap: 7200 / 2h)
Higher = fewer preflightsTradeoff: slower revocation of policy changes

7. Implementing Access-Control-Allow-Credentials

SetupRequirements
Send cookies / AuthorizationServer: Allow-Credentials: true
Origin restrictionCannot use * with credentials
Allow-Headers / MethodsCannot use *
ClientFetch: credentials: "include"

8. Handling Simple vs Preflighted Requests

Simple Request CriteriaAnything Else → Preflight
Method: GET, HEAD, POSTPUT, PATCH, DELETE, etc.
Content-Type: form-urlencoded, multipart/form-data, text/plainapplication/json triggers preflight
Only CORS-safelisted headersCustom headers (X-*, Authorization) trigger preflight

9. Configuring CORS for Multiple Origins

Example: Validated Origin Echo

Set<String> allowed = Set.of(
    "https://app.example.com",
    "https://admin.example.com"
);
String origin = req.getHeader("Origin");
if (origin != null && allowed.contains(origin)) {
    res.setHeader("Access-Control-Allow-Origin", origin);
    res.setHeader("Vary", "Origin");
}
Warning: Always include Vary: Origin when echoing origin to prevent cache poisoning.

10. Debugging CORS Issues

SymptomCause
"No 'Access-Control-Allow-Origin' header"Server not sending CORS headers
"Credentials flag is true, but Allow-Origin is *"Use specific origin
Preflight 4xx/5xxOPTIONS handler missing or auth-protected
Headers not visible to JSAdd to Access-Control-Expose-Headers