Skip to main content

【Debugging Notes】 How to Get a Cookie Sent on a Cross-Origin Request

Background

This is a debugging write-up from my time at cacdi. We were integrating with a partner's ("Compal") API, and that API authenticated every request with a session cookie instead of a bearer token.

localhost calling a third-party domain over GET/POST — does a cookie stored in the browser actually get attached to the request?

The backend already had Access-Control-Allow-Origin configured, so the frontend wasn't blocked by CORS on a plain request. But calling the API from localhost during local development, the browser never sent the cookie along — even though Postman, given the same cookie, could call the API and get a valid response back. So the credential itself was fine. The question was purely about when a browser is willing to attach a cookie to a cross-origin request.

The Setup

  1. The partner's API authenticates via cookie.
  2. The backend already sets Access-Control-Allow-Origin, so the frontend isn't blocked by CORS.
  3. Open question: how do we get a cookie attached when calling that API from localhost?
  4. Postman, given the cookie manually, calls the API successfully and gets a result back.
  5. The frontend needs to set credentials, and the backend needs Access-Control-Allow-Credentials — but that alone didn't explain what was happening in the browser.

To isolate the cookie behavior from the rest of the app, I reproduced it directly in DevTools: manually insert a cookie in the Application panel, then see whether an outgoing request to the partner's domain actually carries it.

Reproducing It in DevTools

Attempt 1 — name & value only

DevTools Application > Cookies panel: a cookie with only Name and Value set, no Domain/Secure/SameSite

Insert just Name and Value, leaving Domain, Secure, and SameSite untouched. Result: the outgoing GET request still doesn't carry the cookie. With none of the attributes explicitly set, the browser doesn't consider it valid for a cross-site request.

Attempt 2 — add SameSite=None

DevTools shows the cookie row highlighted red after setting SameSite=None without Secure — Chrome rejects it

Set SameSite to None. Result: DevTools refuses to save the cookie at all — the row is highlighted red as invalid. Since Chrome 80, SameSite=None is only accepted together with Secure; without Secure, the browser drops it before it's even stored.

Attempt 3 — add Secure

DevTools Application panel: cookie saved successfully with SameSite=None and Secure checked, Domain still shows localhost

Check Secure alongside SameSite=None. Result: the cookie is now saved successfully — but the API call still doesn't include it. Domain is still localhost, which doesn't match the actual request domain, so the browser still won't attach it.

Attempt 4 — match the request's Domain

Network panel Cookies tab: request now carries the cookie once Domain is changed to match the partner's domain

Change Domain to match the request's domain (the partner's domain). Result: the cookie is finally attached to the API call. One side effect worth knowing: once Domain no longer matches the current page's own origin, the cookie disappears from the Application → Cookies panel — you have to check it under Network → (request) → Cookies instead, which is the only place it still shows up.

Set-Cookie: Path=/; Domain=example.com; Secure; SameSite=Strict
  • Domain — must match the request's domain.
  • Secure — if set, the request must be made over HTTPS.
  • SameSite — one of three modes: Strict, Lax (the default), or None.
    • As of Chrome 80 (2020), the default changed from None to Lax.
    • None is only treated as valid when paired with Secure.

The table below (from Chrome's own developer docs) shows which request types still carry a SameSite=Lax cookie cross-site versus the old default behavior — top-level navigations (Link, Form GET) still do, but Form POST, iframe, AJAX, and Image requests no longer do under Lax:

Comparison table: request type vs old default vs SameSite=Lax — Link/Perender/Form GET still fire, Form POST/iframe/AJAX/Image do not

Applying It to the Real Integration

Reproducing the mechanism in DevTools made the actual fix straightforward — it needed changes on both sides, not just one:

  1. Backend: issue the session cookie with Domain, Secure, and SameSite=None set, so the browser is willing to store it and forward it on a cross-site request in the first place.
  2. Frontend: set credentials: 'include' (fetch) or withCredentials: true (axios/XHR) — without this, the browser won't attach any stored cookie to a cross-origin request, no matter how the cookie itself was issued.
  3. Backend: return Access-Control-Allow-Credentials: true. Access-Control-Allow-Origin alone lets the browser send the request; without Allow-Credentials the browser blocks the frontend from reading the response even though the cookie went out.

All three have to be true at once — missing any one of them looks identical from the frontend: the request goes out, but the API responds as unauthenticated.

Key Takeaways

  1. CORS and cookie-attachment are two independent gates. Access-Control-Allow-Origin/Allow-Credentials control whether the browser lets your JS read the response. Domain/Secure/SameSite control whether the browser attaches the cookie to the request in the first place. Both have to pass — fixing one without the other still fails.
  2. Chrome 80 changed the default. Any flow that needs a cookie sent on a cross-site fetch/XHR (not just a top-level navigation like clicking a link) needs SameSite=None; Secure set explicitly — the old implicit behavior no longer applies.
  3. DevTools is a fast way to isolate the mechanism from the app. Manually editing a cookie's Domain/Secure/SameSite in the Application panel let me test each condition in isolation without a real login flow — but remember that once Domain is changed away from the page's own origin, you have to inspect it via Network → Cookies, since it drops out of the Application panel view.

References

  • Chrome Developers — "Get Ready for New SameSite=None; Secure Cookie Settings"
  • MDN — SameSite cookies explainer
  • Chrome's SameSite=Lax default rollout notes for third-party cookies