An HTTP API can carry its credential in a cookie or in an Authorization: Bearer header. Compare the two placements, including how each is attached by the browser and what that means for cross-site request forgery.
answer
- Cookie = ambient, header = explicit
- Ambient attachment causes CSRF
- HttpOnly + Secure + SameSite
- Web-storage token: XSS exfiltrates and reuses anywhere
- Hybrid: Bearer access + HttpOnly refresh cookie
basics
~20 sCookies are attached automatically by the browser to matching requests, which is convenient and allows HttpOnly protection from JavaScript, but that automatic sending is what enables CSRF. A Bearer header must be added explicitly by code, so a forged cross-site request carries none - but the token must be stored where script can reach it.
solid answer
~50 s**Cookies** are *ambient*: once set, the browser attaches them to every matching request regardless of who triggered it. Upsides - the client needs no token-handling code, and `HttpOnly` puts the value out of reach of JavaScript, which blunts theft via XSS. Downside - because they are sent automatically, a form or image on an attacker's page can trigger an authenticated request: that is CSRF. Mitigations are `SameSite=Lax` or `Strict`, an anti-CSRF token or origin checking on state-changing requests, and `Secure`. **`Authorization: Bearer <token>`** is *explicit*: nothing attaches it but your code, so a cross-site request simply arrives unauthenticated and CSRF largely disappears. It also works naturally for non-browser clients and cross-origin calls. The cost is storage: the token lives where script can read it, so XSS can exfiltrate it, and the client must handle refresh and expiry itself. Both are stateless-friendly; placement does not decide whether the server keeps a session.
code
http · 10 linesGET /api/me HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9...
GET /api/me HTTP/1.1
Host: api.example.com
Cookie: session=aBc123...
HTTP/1.1 200 OK
Set-Cookie: session=aBc123...; HttpOnly; Secure; SameSite=Lax; Path=/go deeper
State that cookies are sent automatically and headers must be added by code, and that automatic sending is what makes CSRF possible.
Name the cookie attributes and what each prevents, and explain the XSS-versus-CSRF trade in both directions.
Discuss the hybrid access/refresh split, CORS with credentials, Origin checking, and the blast-radius difference between a stolen HttpOnly session and an exfiltrated web-storage token.
Reason about the client mix - browser, mobile, service-to-service - and decide a platform-wide credential strategy, including where the valuable long-lived credential lives and what a single XSS costs.
## The core difference: ambient versus explicit A cookie is **ambient authority**. After a `Set-Cookie`, the browser attaches it to any request matching its domain, path and attributes - whether that request came from your app, a link, a form on another site, or an image tag. Nothing in your code decides. An `Authorization` header is **explicit**. It exists only because your code put it there on that specific request. A cross-site form post carries no such header. Almost every practical difference follows from this one property. ## Cookies in detail *Attributes that matter*: `HttpOnly` hides the value from `document.cookie`, so XSS cannot simply read it - though script can still *use* the session by making requests, so it is mitigation, not immunity. `Secure` restricts it to HTTPS. `SameSite` controls cross-site attachment: `Strict` never sends on cross-site requests, `Lax` sends on top-level navigations only (so a cross-site GET navigation carries it but a cross-site POST does not), `None` sends always and requires `Secure`. `Domain` and `Path` scope it. *CSRF*: an attacker page issues a POST to your transfer endpoint from a hidden form. The browser attaches the cookie, and the server sees an authenticated request it cannot distinguish from a legitimate one. Defences: `SameSite=Lax`, which removes the common case; a synchroniser or double-submit anti-CSRF token that the attacker cannot read because of the same-origin policy; and checking `Origin` or `Sec-Fetch-Site` on state-changing requests. Note that `SameSite` is a browser behaviour, not a substitute for a server-side check in high-value flows. *Cross-origin*: sending cookies to a different origin requires CORS with credentials included on the client and, on the server, `Access-Control-Allow-Credentials: true` with a specific - never wildcard - `Access-Control-Allow-Origin`. *Fit*: first-party browser apps, especially server-rendered ones, where the same site serves pages and API. ## Bearer headers in detail `Authorization: Bearer <token>` is the standard scheme for tokens: `Authorization` is a core HTTP header, and the Bearer scheme is widely used beyond its OAuth origins. *Security profile*: no ambient attachment, so classic CSRF does not apply. But the token must be stored client-side. Web storage is readable by any script on the origin, so an XSS payload can exfiltrate it and use it from anywhere until it expires - which is worse than XSS against an `HttpOnly` cookie, where the attacker can only act from the victim's browser. Keeping the token in memory only is safer but is lost on page reload. *Ergonomics*: works identically for mobile apps, server-to-server calls, CLI tools and cross-origin browser calls, with no cookie-domain constraints. The client owns refresh, expiry and retry-on-401 logic. *Hygiene*: never put a token in a URL query string - URLs land in server logs, browser history, `Referer` headers and analytics. Always use HTTPS; a Bearer token is a bearer instrument, valuable to whoever holds it. ## A common hybrid Browser apps frequently combine both: a short-lived access token used as a Bearer header for API calls, and the long-lived refresh token in an `HttpOnly; Secure; SameSite` cookie scoped to the refresh endpoint alone. The valuable long-lived credential is unreadable by script; the short-lived one carries little value if stolen; and only the refresh endpoint, which performs no business action, needs CSRF consideration. ## Relationship to statelessness Placement is orthogonal to whether the server holds a session. A cookie can carry a self-contained signed token, and a Bearer header can carry an opaque id the server looks up. Do not conflate cookie with stateful: where the credential travels is a separate question from what it references. ## Answering well Lead with ambient versus explicit, derive CSRF from it, name the mitigating attributes, then present the XSS trade honestly in both directions, and finish with the hybrid and the note that placement does not imply server-side session storage.
- Is storing a token in browser local storage safe if the site has no XSS vulnerability?It is a bet that no script on the origin will ever be compromised, including third-party dependencies, analytics and ad tags, since any of them can read local storage. An HttpOnly cookie limits a successful XSS to acting from the victim's browser rather than exporting a reusable credential, so it fails less badly. Keeping the token only in memory, with a refresh credential in an HttpOnly cookie, is the usual compromise.
- Does SameSite=Lax remove the need for anti-CSRF tokens?It removes the common cross-site form-POST case in browsers that enforce it, but it is a client-side behaviour: older or non-conforming clients, and same-site-but-untrusted subdomains, are not covered, and Lax still permits top-level cross-site GET navigations. Keep a server-side check - an anti-CSRF token or Origin/Sec-Fetch-Site validation - on state-changing endpoints and treat SameSite as defence in depth.
saying these in an interview costs you the question
- Believing Bearer tokens are simply more secure than cookies, ignoring the XSS exfiltration trade
- Assuming an HttpOnly cookie makes XSS harmless rather than merely limiting it
- Putting a token in a URL query parameter where it reaches logs, history and Referer headers
- Treating cookies as inherently stateful and Bearer tokens as inherently stateless
- Setting Access-Control-Allow-Origin to * while trying to send credentialed cross-origin requests