CORS診断実習ラボ:OPTIONSプリフライト解析とレスポンスヘッダー設定
ブラウザが送信するOPTIONSプリフライトリクエストの条件を理解し、セキュアなCORSレスポンスヘッダーを正しく設定できるようになる。
1. Production Scenario & Incident
フロントエンド https://app.example.com からバックエンドAPI https://api.example.com へのAPI通信がCORSポリシー違反で拒絶されています。
2. Architectural Background & RFC Standards
ブラウザは非単純リクエスト送信前にOPTIONSリクエストを発行し、サーバーがAccess-Control-Allow-OriginおよびHeadersを許可しているか確認します。
- Origin Definition: An origin consists of Scheme + Hostname + Port (e.g., `https://` + `example.com` + `:443`). Any variation makes it a cross-origin request.
- Simple vs Preflighted Requests: Requests with custom headers (e.g., `Authorization`) or non-simple Content-Types (e.g., `application/json`) trigger an automatic preflight `OPTIONS` request.
- Preflight Handshake: The browser asks: "Can I send a POST with JSON from origin X?" The server responds with `Access-Control-Allow-Origin`, `Access-Control-Allow-Methods`, and `Access-Control-Allow-Headers`.
- Credentials Restriction (RFC 6454): If `Access-Control-Allow-Credentials: true` is set, `Access-Control-Allow-Origin` CANNOT be the wildcard `*`. It must be an explicit origin.
3. Hands-On Lab Instructions
Inspect the simulated preflight OPTIONS request in the interactive CORS workbench.
Note why the request is preflighted: it sends `Content-Type: application/json` and custom telemetry headers.
Respond to the preflight OPTIONS request with HTTP status 204 No Content and appropriate `Access-Control-Allow-*` headers.
Configure `Access-Control-Allow-Origin` to dynamically validate against an approved origin whitelist instead of reflecting `*`.
Set `Access-Control-Max-Age: 86400` to cache the preflight handshake for 24 hours, reducing API latency.
4. Interactive Lab Workbench
Simulate cross-origin browser fetch calls. Inspect preflight OPTIONS negotiation, origin header verification, and credential constraints.
Server must return 204 No Content on OPTIONS with Access-Control-Allow-Origin: https://app.curious-techie.com and Access-Control-Allow-Credentials: true
5. Diagnostic Comprehension Questions
Why does a request fail when both "Access-Control-Allow-Origin: *" and "Access-Control-Allow-Credentials: true" are returned?
Reveal Answer & Explanation →
✓ Correct Answer: W3C CORS specifications explicitly prohibit wildcard origins when credentials (cookies, HTTP auth) are permitted, to prevent arbitrary malicious sites from harvesting authenticated data.
Under modern browser security standards, if a site allows cookies or credentials to be sent cross-origin, it must explicitly name the trusted origin. Pairing a wildcard "*" with credentials creates a catastrophic CSRF data leakage flaw and is strictly blocked by the browser.
If a CORS preflight error occurs, which entity blocked the response: the server or the browser?
Reveal Answer & Explanation →
✓ Correct Answer: The browser client engine blocked the JavaScript application from reading the response because the server response headers did not grant permission.
CORS is enforced entirely by client web browsers. The server often processes the request and returns data, but the browser security sandbox inspects the CORS response headers and prevents client-side JavaScript from reading the data if permission headers are missing.
6. Progressive Diagnostic Hints
💡 Hint #1▼
💡 Hint #2▼
💡 Hint #3▼
7. Verified Production Solution
APIサーバー側でOPTIONSリクエストに対して適切なAccess-Control-Allow-*ヘッダーとステータス204/200を返却するよう構成します。
// Node.js Express / Fastify Production CORS Middleware
const ALLOWED_ORIGINS = new Set([
'https://app.curious-techie.com',
'https://admin.curious-techie.com',
'https://staging.curious-techie.com'
]);
export function corsMiddleware(req, res, next) {
const origin = req.headers.origin;
// Verify origin against strict whitelist
if (origin && ALLOWED_ORIGINS.has(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader('Vary', 'Origin'); // Critical for HTTP caching proxies!
}
// Handle Preflight OPTIONS request
if (req.method === 'OPTIONS') {
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With');
res.setHeader('Access-Control-Max-Age', '86400'); // Cache preflight for 24 hours
return res.status(204).end();
}
next();
}- ✓1. Inspect the incoming `Origin` request header sent automatically by the browser.
- ✓2. Validate the origin against an exact whitelist of trusted domains (never blindly reflect `req.headers.origin` without checking).
- ✓3. If valid, set `Access-Control-Allow-Origin` to that specific origin and set `Access-Control-Allow-Credentials: true`.
- ✓4. Set `Vary: Origin` to ensure intermediate reverse proxies and CDNs do not serve a cached CORS response meant for one origin to another.
- ✓5. Intercept `OPTIONS` HTTP requests, attach allowed methods and allowed headers, and respond immediately with HTTP 204 No Content.
8. In-Depth Technical Explanation
"If an application initiates a non-simple cross-origin request without server-side OPTIONS handling, the browser halts request processing during preflight; if the server subsequently emits Access-Control-Allow-Origin: * alongside Allow-Credentials: true, the deterministic outcome is a fatal W3C specification violation in the browser network layer."
Underlying Root Cause Analysis
CORS errors occur because browsers protect users through the Same-Origin Policy. When client JavaScript sends a request to a different domain, protocol, or port, the browser enforces CORS. If the request is complex (such as sending JSON or an Authorization token), the browser executes an automatic preflight OPTIONS request. If the server does not return the proper `Access-Control-Allow-*` headers or misconfigures credential rules, the browser blocks the response.
Production Engineering Best Practices
- →Always set the `Vary: Origin` header when dynamically returning `Access-Control-Allow-Origin` to avoid CDN cache poisoning.
- →Cache preflight checks using `Access-Control-Max-Age: 86400` (Chromium caps this at 7200s, Firefox at 86400s).
- →Never blindly reflect the `Origin` header without whitelist validation, as this completely bypasses the Same-Origin Policy.
Common Pitfalls to Avoid
- ✕Setting `Access-Control-Allow-Origin: *` and then wondering why cookies or auth headers fail to send.
- ✕Writing CORS handlers that only run on successful 200 responses, causing error responses (404, 500) to fail with cryptic CORS errors.
- ✕Forgetting to allow custom client headers in `Access-Control-Allow-Headers`.
関連技術エコシステムと次の学習ステップ
関連技術解説記事
CSP vs CORS: Differences in Browser Defense Mechanisms
Distinguishing outbound resource loading restrictions from inbound server access control.
Troubleshooting: No Access-Control-Allow-Origin Header Present
Diagnose and fix the most common browser cross-origin blocking error with production configs.
関連する技術Q&A
Why Does CORS Allow Simple GET Requests Without a Preflight?
Answer: CORS allows simple GET requests without preflight for backwards compatibility with legacy HTML form and image behaviors that predate CORS.
Why Do Browsers Send OPTIONS Preflight Requests?
Answer: Browsers send preflights to verify that older servers not expecting cross-origin calls explicitly permit the method and custom headers before execution.
開発者向け規格・仕様リファレンス
次のステップへ進む
CORS Policy & Preflight Handshake Lab
Simulate preflight OPTIONS probes and adjust Access-Control-Allow-* headers inside the browser sandbox.