Rapid-Fire: CORS, Cookies and Cross-Origin
The area where practical experience is most obvious, because the failures are so specific.
Frontend on abc.com, API on dfg.com. What changes?
Three mechanisms activate at once. CORS, because the origins differ — the browser hides the response unless the API opts in. Cookie scope, because dfg.com can only set cookies for dfg.com; it can never set one on abc.com. And SameSite, because the two are different sites, so only a SameSite=None; Secure cookie is attached — which makes it a third-party cookie that Safari blocks and Firefox partitions.
Is CORS protecting your API?
No. It is enforced by browsers only; curl ignores it entirely. CORS protects the user's session from being read by a page they did not trust. It is not an access control and never replaces authentication.
What triggers a preflight?
Anything not "simple": a method other than GET/HEAD/POST, any non-safelisted header — Authorization, X-API-Key, or Content-Type: application/json. In practice almost every real API call preflights, which is why Access-Control-Max-Age matters.
Why can't you use Access-Control-Allow-Origin: * with credentials?
Because it would mean every site on the internet could read authenticated responses using the victim's own session. The browser refuses. You must echo an exact origin from an allowlist — and matching with endsWith('abc.com') is a bypass, since evil-abc.com matches.
Login returns 200, the next call returns 401. Where do you look?
The request headers in devtools, not the console. If Cookie is absent the browser dropped it, and the server is irrelevant. Then check, in order: credentials: 'include' on the login call, Access-Control-Allow-Credentials on the response, SameSite=None; Secure on the cookie, and finally third-party cookie blocking — which produces no console error at all.
What is CSRF and why don't bearer tokens have it?
CSRF exploits ambient credentials: the browser attaches cookies automatically, so another site can cause an authenticated request. A bearer token is attached by your code, and another origin's JavaScript cannot read it, so the attack has nothing to work with.
So which do you choose for abc.com → dfg.com?
First, try to stop being cross-site: move the API to api.abc.com or proxy it at abc.com/api. Cookies become first-party and the whole category disappears. If you genuinely cannot, use bearer tokens — they fail in fewer places than cross-site cookies do.
Example
# The distinction the whole area rests on
ORIGIN = scheme + host + port → what CORS cares about
SITE = registrable domain → what COOKIES care about
app.abc.com → api.abc.com different origin, SAME site ✅ Lax cookies work
abc.com → dfg.com different origin, DIFF site ❌ needs None+Secure
abc.com:3000→ abc.com:8000 different origin, SAME site ✅
http://abc → https://abc different origin, DIFF site ⚠️ scheme counts
# One CNAME moving the API to api.abc.com deletes: SameSite=None, Partitioned,
# third-party cookie support, and the Safari explanation.When to use it
- A candidate is asked why something works in Postman but not the browser and immediately names CORS and SameSite rather than guessing.
- An interviewer asks how to debug a missing cookie and the candidate says 'check the request headers, not the console'.
- A design question about a widget embedded on customer sites leads the candidate to tokens because third-party cookies cannot be relied on.
More examples
The CORS misconfiguration they will show you
This snippet appears in interviews constantly because it looks reasonable and is catastrophic. Ranking the three findings by severity is part of the answer.
// "Review this. Anything wrong?"
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', req.headers.origin);
res.header('Access-Control-Allow-Credentials', 'true');
res.header('Access-Control-Allow-Headers', '*');
next();
});
// Three findings, in severity order:
//
// 1. CRITICAL — the origin is reflected with no allowlist. Combined with
// credentials:true, ANY site can read authenticated responses using the
// victim's session. Full account takeover from any page they visit.
//
// 2. Vary: Origin is missing. A CDN or shared cache will serve the
// allow-origin computed for one site to a request from another.
//
// 3. Allow-Headers '*' is ignored when credentials are involved — the wildcard
// is not honoured in credentialed mode, so it also does not work.
// The fix:
const ALLOWED = new Set(['https://abc.com', 'https://www.abc.com']);
app.use((req, res, next) => {
const origin = req.get('origin');
if (origin && ALLOWED.has(origin)) { // exact match, from a set
res.set('Access-Control-Allow-Origin', origin);
res.set('Access-Control-Allow-Credentials', 'true');
}
res.set('Vary', 'Origin'); // always, even when denied
if (req.method === 'OPTIONS') {
res.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.set('Access-Control-Max-Age', '86400');
return res.status(204).end();
}
next();
});
// And the follow-up they will ask: "what about endsWith('abc.com')?"
// → https://evil-abc.com matches. Parse the URL and compare the hostname,
// or match against a set of exact strings.Debugging live, out loud
Interviewers score the method here more than the answer. Narrating an ordered elimination beats naming the right cause by luck.
# "The login works but every API call after it is 401. Debug it."
# Narrate the method, not guesses.
# 1. Does the server work at all? (removes half the search space)
curl -i https://dfg.com/api/notes -H "Cookie: sid=$SID"
# 200 → the server is fine, the BROWSER is dropping the credential
# 401 → the server is rejecting it; look at the session store
# 2. Is the credential actually leaving the browser?
# DevTools → Network → the failing request → Request Headers
# No 'Cookie' line? The browser dropped it. Nothing server-side matters yet.
# 3. Why did the browser drop it? In order of likelihood:
# a. credentials:'include' missing on the call — or on LOGIN, so it was
# never stored in the first place
# b. response lacked Access-Control-Allow-Credentials: true
# c. cookie lacked SameSite=None; Secure and the sites differ
# d. the browser blocks third-party cookies — NO console error at all
# 4. Confirm (d) in ten seconds:
# Open Safari. If it fails there and works in Chrome, that is your answer,
# and no server change will fix it.
# 5. Chrome states the reason directly:
# DevTools → Application → Cookies → hover the yellow triangle
# Saying "I'd check the request headers before the response" is the sentence
# that shows you have actually debugged this rather than read about it.The design question this always becomes
Reframing before answering shows seniority. Junior candidates configure what they were handed; senior ones question whether the constraint is real.
# "Our SPA is on abc.com and our API is on dfg.com. How do you do auth?"
# Lead with the option they did not ask about:
"First I'd ask whether they have to be different sites. If we control the DNS,
moving the API to api.abc.com or proxying it at abc.com/api makes cookies
first-party — SameSite=Lax, no third-party cookie problem, no Safari
special case. That is a DNS change and it deletes the entire problem."
# Then, if genuinely cross-site, rank:
1. BFF, if there is a frontend server (Next.js, Laravel, nginx)
browser holds a first-party session cookie; tokens stay server-side.
Strongest browser story — an XSS cannot steal a portable credential.
2. Bearer tokens
access token in memory, refresh in an HttpOnly cookie.
Works everywhere; no CSRF by construction; more client code.
3. Cross-site cookies with SameSite=None; Secure; Partitioned
Least code, and it silently fails for a real share of users.
Only when something forces it — and test Safari on day one.
# Close with the number:
"Whichever we pick, the access token lives 10 minutes, so a ban takes effect
within 10 minutes. If that is too slow we add a tokenVersion check with a
five-second cache."
Discussion