🔄 The Cross-Origin Request That Forgot to Bring Its Cookies
A fetch call defaults to not sending cookies on a cross-origin request at all unless you explicitly opt in on the client, and the server on the other end has to agree with a matching header pair naming the exact calling origin rather than a wildcard. Miss either half of that handshake and the request often still succeeds with a normal success status, just without the session cookie attached, so the API quietly treats a logged-in caller as logged out instead of returning anything that looks like an authentication error.
🔎 The Problem
// Frontend served from app.example.com calling api.example.com -
// a cross-origin request by definition, even though both are "yours."
fetch('https://api.example.com/account', {
method: 'GET'
// no credentials option set - defaults to same-origin, which
// means NO cookies are sent to a different origin at all.
})
.then(res => res.json())
.then(data => console.log(data)); // { loggedIn: false } every time,
// even for a user who is very much logged in on api.example.com.
// Fixed: explicitly opt in on the client...
fetch('https://api.example.com/account', {
method: 'GET',
credentials: 'include'
});
// ...and the server has to agree, with the exact origin, never a
// wildcard, plus an explicit allow-credentials header set to true.
✅ Fix: Opt In on Both the Client and the Server
- Add the credentials option set to include on every fetch call that needs to carry the session cookie across origins – the browser’s same-origin default silently drops cookies on a cross-origin request rather than raising any error you would notice in the client code itself.
- On the server, respond with the exact calling origin in the allow-origin header, never a wildcard, together with an explicit allow-credentials header set to true – a wildcard origin is specifically disallowed by browsers the moment credentials are involved, and the request fails differently if you try it anyway.
- Treat a cross-origin response that succeeds but shows a logged-out state as a credentials configuration problem first, before assuming the session itself expired – check the actual request headers for a cookie header before debugging anything server-side.
⚠️ Why This Is Easy to Miss
- The request still returns a normal success status with a valid-looking response body, so nothing about the failure looks like a networking or cross-origin error – it looks exactly like the user really is logged out, which sends debugging in the wrong direction first.
- A same-origin development setup, where a local proxy makes the frontend and API appear as one origin, never exhibits this at all, so the bug only appears after deploying to an environment where the frontend and API are genuinely on different domains or subdomains.
A cross-origin request that forgets its cookies does not fail loudly – it just quietly agrees with the server to pretend nobody is logged in.
