Credential Sync Across Subdomains: CORS Preflight & Cookie Isolation Workflows
Browser-enforced mechanics for sharing authentication credentials across subdomains require strict adherence to CORS preflight lifecycles and cookie domain scoping. This guide details secure header orchestration for credentialed cross-origin requests.
- Browser credential isolation rules for cross-origin requests
- Preflight request lifecycle for credentialed fetches
- Subdomain cookie domain scoping strategies
- Debugging cross-origin session synchronization failures
Preflight Mechanics for Credentialed Subdomain Requests
When credentials: 'include' is set, browsers automatically trigger an OPTIONS preflight for non-simple cross-subdomain requests. The server must validate headers before the actual payload proceeds.
Strict origin matching is mandatory per WHATWG Fetch standards. The Access-Control-Allow-Origin response header cannot use a wildcard. It must reflect the exact requesting origin string, including scheme, host, and port.
Caching credentialed responses requires the Vary: Origin header. Omitting it causes shared proxies to serve incorrect origin values to subsequent subdomain requests.
Programmatic allowlisting scales better than static configuration. Implementing Dynamic Origin Validation Patterns ensures only authorized subdomains receive credential headers.
const ALLOWED_SUBDOMAIN_PATTERN = /^https:\/\/[a-z0-9-]+\.example\.com$/;
app.use((req, res, next) => {
const origin = req.headers.origin;
if (origin && ALLOWED_SUBDOMAIN_PATTERN.test(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader('Vary', 'Origin');
}
next();
});
This middleware safely reflects validated subdomain origins while enforcing credential flags and cache variation headers. The regex is anchored with ^ and $ to prevent substring bypass attacks.
Cookie Domain Scoping & SameSite Constraints
Cross-subdomain session propagation relies on precise HTTP cookie attributes. The Domain attribute must be explicitly set to .example.com (with the leading dot). This allows propagation across app.example.com and api.example.com.
Modern browsers enforce strict SameSite policies. Credentialed cross-origin API calls require SameSite=None; Secure. Without this combination, cookies are silently dropped on cross-origin requests, even when CORS headers are correct.
Ensure TLS termination handles the Secure flag correctly. Mixed-content deployments (http API from an https page) reject SameSite=None cookies entirely.
Set-Cookie: session_id=abc123; Domain=.example.com; Path=/; Secure; SameSite=None; HttpOnly
This header guarantees the cookie attaches to credentialed cross-subdomain requests while resisting XSS extraction via HttpOnly.
Debugging Workflows & Network Trace Analysis
Credential sync failures typically manifest as opaque network errors or explicit 401/403 responses. Distinguish between CORS preflight blocks and application-level authentication failures.
Use browser DevTools Network tab with “Disable Cache” enabled. Filter by XHR/Fetch and inspect the OPTIONS request headers. Verify the presence of Origin and confirm that Access-Control-Request-Credentials is not a real header (it doesn’t exist — credentials mode is conveyed through the Fetch API options, not a request header).
Trace Set-Cookie mismatches by checking the Domain and Path attributes in the response headers. Cookies scoped to api.example.com will not sync to app.example.com — the Domain attribute must be .example.com to cover both.
Implementation Patterns for Cross-Subdomain Auth
Server-side origin reflection provides flexibility but requires strict validation. Static allowlists offer higher security guarantees for fixed infrastructure deployments.
The credentials flag in fetch() or XMLHttpRequest dictates browser behavior. Setting it to include attaches cookies and HTTP authentication headers automatically.
Wildcard origins combined with credential flags violate WHATWG Fetch standards. Browsers will block the response and log a console error immediately.
Aligning client and server configurations with Server-Side CORS Configuration & Header Management principles ensures predictable session synchronization across distributed endpoints.
fetch('https://api.example.com/data', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' }
});
This configuration triggers the credentialed preflight (because Content-Type: application/json is non-safelisted) and ensures session cookies attach to the cross-subdomain payload.
Common Mistakes
| Issue | Explanation |
|---|---|
Using Access-Control-Allow-Origin: * with credentials: true |
Browsers explicitly reject wildcard origins when credentials are included, causing immediate preflight failure. |
Omitting Vary: Origin on credentialed responses |
Caches may serve incorrect origin headers to subsequent requests, breaking credential sync for other subdomains. |
Setting SameSite=Lax for cross-subdomain API calls |
Lax restricts cookies to top-level navigations, blocking credentialed API requests and causing silent auth failures. |
Cookie Domain scoped to a specific subdomain |
Domain=api.example.com restricts the cookie to that subdomain only; use Domain=.example.com for cross-subdomain sharing. |
FAQ
Does Access-Control-Allow-Credentials work with wildcard origins?
No. Browsers strictly forbid combining credentials: true with Access-Control-Allow-Origin: * due to credential exposure risks.
How does SameSite=Lax affect cross-subdomain preflight requests?
Lax blocks cookies on cross-origin API requests that are not top-level navigations. Credentialed preflights will fail because the session cookie is not sent, causing auth middleware to reject the request before CORS headers are applied.
Why do credentialed OPTIONS requests fail with 403?
Typically caused by missing Access-Control-Allow-Credentials: true, incorrect origin matching, or server-side auth middleware intercepting the preflight before CORS headers are applied. Auth middleware must be bypassed for OPTIONS requests.
Can I sync JWTs via localStorage instead of cookies for CORS?
Yes. Storing JWTs in localStorage and sending them as Authorization: Bearer <token> bypasses cookie mechanics. However, custom headers trigger preflight, so the server must allowlist Authorization in Access-Control-Allow-Headers.