Third-Party Cookies Blocked — Iframe Login State Silently Lost
Debug cross-site iframes losing session cookies as browsers phase out third-party cookies, verify with partitioned storage tests, and migrate to token patterns.
Symptoms
- An embedded iframe (payment form, SSO widget, embedded app) logs in successfully but shows logged-out state on next interaction
- Reproduces in Safari and Chrome with third-party cookie blocking, works in profiles where blocking is disabled
- No errors appear: the cookie simply is not sent with the iframe requests
Possible causes
- The session cookie is set in a third-party context, which modern browsers block or partition by default
- SameSite=Lax or Strict cookies are never sent in cross-site iframe requests regardless of third-party policy
- Storage APIs in the iframe are partitioned, so prior "localStorage session" reads fail silently
- The embedded flow depends on top-level navigation semantics that iframe contexts do not provide
Diagnosis steps
Compare cookie behavior across profiles
Chrome: Settings → Privacy → "Block third-party cookies" toggle; Safari default blocksInterpretation
Symptom present with blocking on and absent with it off is the third-party cookie dependency proven in two minutes.
Inspect what the iframe requests actually carry
DevTools → Network → filter the iframe origin → check the Cookies column on its requestsInterpretation
Requests without the session cookie confirm non-delivery; SameSite column shows whether policy or blocking is the reason.
Audit cookie attributes
curl -sI https://widget.example.com/login | grep -i "set-cookie"Interpretation
Missing SameSite=None, missing Secure, or a session scoped to top-level context explains the silence.
Root cause
Browsers isolate cookies by top-level site: a cookie set inside a cross-site iframe belongs to a third-party context, which is blocked or partitioned by default now. The iframe therefore performs authenticated requests without any session cookie attached, and the server treats each request as anonymous.
Fix
Move session establishment out of cookie-in-iframe: pass short-lived tokens via postMessage to the iframe origin, use Storage Access API where user interaction is acceptable, or restructure the flow so authentication happens in a top-level popup and returns a token. Set SameSite=None; Secure only for cookies that genuinely must cross sites.
Risk note
Tokens via postMessage widen the attack surface if origins are not strictly validated on both ends. Allowlist exact origins and validate every message.
Verify the fix
With third-party blocking forced on, the iframe flow authenticates via token exchange, requests carry the token, and the session persists across reloads in Safari and Chrome.
Symptom walkthrough
The silence is the signature — no error, no failed request, just anonymous behavior:
DevTools → Network → iframe requests to widget.example.com
POST /api/session Cookies: (none)
The iframe logged in moments ago, and now its API calls carry no cookie at all. The two-minute experiment settles attribution: toggle the browser third-party cookie setting. Works with blocking off, anonymous with blocking on — the dependency is proven. The SameSite column adds the second layer: even without blocking, Lax and Strict cookies never ride cross-site iframe requests at all.
Fix in practice
Token handoff replaces cookie inheritance:
// parent (app.example.com):
const iframe = document.querySelector('iframe');
iframe.addEventListener('load', () => {
iframe.contentWindow.postMessage(
{ type: 'auth', token: shortLivedToken },
'https://widget.example.com' // exact origin, never '*'
);
});
// iframe (widget.example.com):
window.addEventListener('message', (event) => {
if (event.origin !== 'https://app.example.com') return; // validate
if (event.data?.type === 'auth') startSession(event.data.token);
});
For storage-dependent widgets, the Storage Access API requests user permission for unpartitioned access — acceptable when a visible interaction exists. And for heavy flows, restructure: top-level popup for authentication, postMessage the token back, iframe consumes it. Both ends of every postMessage must validate origins; a wildcard target turns a session fix into a session leak.