cors-acah-allowlist-lesson
Type: lesson-learned
Date: 2026-07-29
Project: oscar-web (FES inspector + Edge Tools panel work)
Thread: oscar-website (415655444668635)
CORS ACAH Allow-list — Gotcha
2026-07-29
**The gotcha**: When the browser sends a request with a custom header (X-Fv-App), the preflight's Access-Control-Allow-Headers response header MUST enumerate that custom header. Otherwise the browser blocks the actual request with:
'Request header field X is not allowed by Access-Control-Allow-Headers in preflight response'.
**Diagnosis path**:
- 'blocked by CORS policy' → ACAO issue (origin not in allow-origin)
- 'not allowed by Access-Control-Allow-Headers' → ACAH issue (custom header not enumerated)
- 'No Access-Control-Allow-Origin header' → missing ACAO entirely
**Fix** (nginx on IONOS VPS):
```nginx
add_header Access-Control-Allow-Headers 'Content-Type, Authorization, X-Fv-App' always;
```
Then nginx -t && systemctl reload nginx.
**Why I got this wrong twice**:
1. First, I claimed 'custom headers don't need preflight' — wrong. ANY non-simple header triggers preflight, and ACAH must allow it.
2. Then, I claimed 'the preflight only enumerates headers sent with the preflight' — wrong. The preflight enumerates headers the browser WILL send on the actual request.
**Right mental model**:
- ACAO (allow-origin) controls WHO can call
- ACAH (allow-headers) controls WHAT HEADERS they can send
- ACAM (allow-methods) controls WHICH HTTP VERBS they can use
- All three must permit the call, or the browser blocks it.
**Workaround**: use ?app= query param (doesn't trigger preflight). Header pattern is preferred for security (harder to spoof) but requires the ACAH fix.