1---2name: cors3description: Configure Cross-Origin Resource Sharing correctly to avoid security issues and debugging pain.4---56## Preflight Triggers78- Any header except: Accept, Accept-Language, Content-Language, Content-Type (with restrictions)9- Content-Type other than: application/x-www-form-urlencoded, multipart/form-data, text/plain10- Methods: PUT, DELETE, PATCH, or any custom method11- ReadableStream in request body12- Event listeners on XMLHttpRequest.upload13- One trigger = preflight; simple requests skip OPTIONS entirely1415## Credentials Mode1617- `Access-Control-Allow-Origin: *` incompatible with credentials—must specify exact origin18- `Access-Control-Allow-Credentials: true` required for cookies/auth headers19- Fetch: `credentials: 'include'`; XHR: `withCredentials = true`20- Without credentials mode, cookies not sent even to same origin for cross-origin requests2122## Wildcard Limitations2324- `*` doesn't match subdomains—`*.example.com` is invalid, not a pattern25- Can't use `*` with credentials—specify origin dynamically from request26- `Access-Control-Allow-Headers: *` works in most browsers but not all—list explicitly for compatibility27- `Access-Control-Expose-Headers: *` same issue—list headers you need to expose2829## Origin Validation3031- Check Origin header against allowlist—don't reflect blindly (security risk)32- Regex matching pitfall: `example.com` matches `evilexample.com`—anchor the pattern33- `null` origin: sandboxed iframes, file:// URLs—usually reject, never allow as trusted34- Missing Origin header: same-origin or non-browser client—handle explicitly3536## Vary Header (Critical)3738- Always include `Vary: Origin` when response depends on origin—even if you allow only one39- Without Vary: CDN/proxy caches response for one origin, serves to others—breaks CORS40- Add `Vary: Access-Control-Request-Headers, Access-Control-Request-Method` for preflight caching correctness4142## Exposed Headers4344- By default, JS can only read: Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, Pragma45- Custom headers invisible to JS unless listed in `Access-Control-Expose-Headers`46- `X-Request-ID`, `X-RateLimit-*`, etc. need explicit exposure—common oversight4748## Preflight Caching4950- `Access-Control-Max-Age: 86400` caches preflight for 24h—reduces OPTIONS traffic significantly51- Chrome caps at 2 hours; Firefox at 24 hours—values above are silently reduced52- Cached per origin + URL + request characteristics—not globally53- Set to 0 or omit during development—caching hides config changes5455## Debugging5657- CORS error in browser = request reached server and came back—check server logs58- Preflight failure: server must return 2xx with CORS headers on OPTIONS—404/500 = failure59- Opaque response in fetch: `mode: 'no-cors'` succeeds but response is empty—usually not what you want60- Network tab shows CORS errors; Console shows which header is missing6162## Common Server Mistakes6364- Only setting CORS headers on main handler, not OPTIONS—preflight fails65- Setting headers after error response—CORS headers missing on 4xx/5xx breaks error handling66- Proxy stripping headers—verify headers reach client, not just that server sets them67- `Access-Control-Allow-Origin: "*", "https://example.com"`—must be single value, not list6869## Security7071- Don't reflect Origin header blindly—validate against allowlist first72- Private Network Access: Chrome requires `Access-Control-Allow-Private-Network: true` for localhost access from public web73- CORS doesn't prevent request from being sent—just blocks response reading; server still processes it74- Sensitive endpoints: don't rely on CORS alone; use authentication + CSRF tokens