← Debugging Guides

阅读中文版本

SameSite=Lax — Cookies Missing on Cross-Site POST

Diagnose sessions that work for navigation but vanish on cross-site POSTs and iframes, understand Lax default semantics, and fix flow structure or cookie policy.

beginnerdebugginghttpcookiessamesite

Symptoms

  • A payment provider POST-back or OIDC callback arrives without the session cookie and logs the user out
  • Top-level navigation keeps the session (GET links work) while POST submissions from other sites lose it
  • The flow works in older browsers or with SameSite disabled test profiles

Possible causes

  • SameSite=Lax (the modern default) sends cookies only on top-level GET navigation, not cross-site POST
  • A return-POST handshake (payment callback, legacy SSO) relies on cookie presence that Lax forbids
  • An embedded iframe context is neither top-level nor same-site, so Lax withholds the cookie
  • A cross-site subresource fetch (XHR to another site) carries no credentials under Lax by design

Diagnosis steps

Compare cookie presence across request types

DevTools → Network → the cross-site POST → Request Headers → Cookie section

Interpretation

Cookie present on same-site GET, absent on cross-site POST, is the Lax boundary demonstrated in one panel.

Read the cookie attributes

curl -sI https://app.example.com/login | grep -i set-cookie

Interpretation

SameSite=Lax or an absent attribute (defaults to Lax in modern browsers) explains POST omission without any bug in the session store.

Classify the flow shape

grep -Rn "redirect\|callback\|returnUrl" src/routes/ | head

Interpretation

Whether the third-party returns via POST-back or redirect determines the fix: reshape the flow or explicitly relax the cookie.

Root cause

SameSite=Lax allows cookies only on same-site requests and top-level navigations using safe methods (GET). A cross-site POST — payment callback, form auto-submit from another origin — is neither, so the browser withholds the session cookie and the receiving endpoint treats the user as anonymous.

Fix

Prefer reshaping flows to top-level GET redirects (return a redirect URL instead of a POST-back, or use a one-time token in the URL validated server-side). When POST-back is unavoidable, set SameSite=None; Secure for the specific cookie, accepting the broader exposure deliberately.

Risk note

SameSite=None on the session cookie reopens cross-site request inclusion that Lax closed, raising CSRF exposure. Add token-based CSRF defenses if you relax the attribute.

Verify the fix

The callback flow completes with the session intact; cross-site POSTs carry the cookie only for the deliberately relaxed one, and CSRF protections still block forged requests.

Symptom walkthrough

The split is the tell: session works for every normal navigation, but the moment a third party POSTs back (payment provider, legacy SSO), the request arrives anonymous. The Network panel shows it plainly — same-site GET carries the cookie, cross-site POST carries nothing. And the Set-Cookie line explains why without any server bug:

Set-Cookie: session=abc123; Path=/; HttpOnly; SameSite=Lax

Lax permits top-level GET navigation; it withholds on cross-site POST and in iframes. Browsers defaulting to Lax converted previously-working POST-back flows into silent logouts.

Fix in practice

Reshape the flow when possible — GET redirect with a server-validated one-time token beats POST-back under modern cookie policy:

provider -> redirect GET /payments/callback?token=one-time-id
server: validate token, look up pending operation, apply, redirect onward

When the POST-back is fixed by an external provider, relax the one cookie deliberately and pair it with CSRF defenses:

Set-Cookie: session=abc123; Path=/; HttpOnly;
            Secure; SameSite=None

SameSite=None must carry Secure (browsers reject None without it), and every None you set re-admits cross-site sending — so keep the count of relaxed cookies to the minimum the integration truly requires, and verify CSRF tokens still guard state-changing routes.