Why does a cookie deleted through a framework's delete helper often reappear on the next request, and what must the deletion match?
answer
- deletion is just another write
- the client matches more than the name
- name, domain and path together
- defaults on delete may differ from create
- something may re-issue it afterwards
basics
~20 sA delete helper erases nothing: it writes the cookie name back already expired. The client drops a stored cookie only when name, domain and path all match, so a deletion using the framework's default scope misses a differently scoped cookie.
solid answer
~50 sThere is no "delete cookie" operation on the wire. The helper writes another `Set-Cookie` for that name with an expiry already in the past, and the browser removes the stored entry only if it matches on **name plus domain plus path**. When the original was written with a narrower path or an explicit parent domain - often by a different component with its own configuration - and the delete call inherits the application's defaults, the two do not match: the browser drops nothing and keeps sending the original. The fix is to expire it with exactly the scope it was created with, and to write and delete each cookie through one place so the scope cannot drift. A deletion also fails silently if the response never reaches the client, if another component re-issues the cookie later on the same response, or if the deletion carries attributes the client rejects.
code
http · 2 linesSet-Cookie: remember=abc123; Path=/account; Domain=app.example.com; HttpOnly
Set-Cookie: remember=; Path=/; Max-Age=0; HttpOnlygo deeper
Remember that deleting a cookie means writing it again already expired, and that the write must carry the same name, domain and path it was created with.
Explain the matching triple and why default-filled attributes on the delete call are the usual cause; show how you would compare the creating response with the deleting one.
Demonstrate the diagnosis: capture the raw headers, check the browser's stored entry, look for a component that re-issues the cookie, and pair the expiring write with server-side invalidation.
Own the shape that prevents it: one owner per cookie name holding its scope, a deliberately small set of distinct scopes, and a policy that client-side expiry is cleanup rather than a security control.
## Deletion is a write, not a removal Nothing in the cookie mechanism lets a server reach into the client's store and remove an entry. The only tool is another `Set-Cookie` response header. A framework's delete helper therefore writes the cookie name back with an empty value and an expiry that has already passed, and the browser, on processing it, discards the matching entry. Everything that can go wrong follows from that one fact: **a deletion is subject to exactly the same matching rules as the write that created the cookie.** The client identifies a stored cookie by a triple: 1. the **name**; 2. the **domain** it was stored for; 3. the **path** it was stored under. An expiring write that differs in any of the three is not a deletion of the original at all - it is a brand-new, already-expired cookie, which the browser stores nowhere and which changes nothing. The original stays put and keeps arriving on every matching request, so the handler that just "deleted" it reads it again on the next call. ## Why the scope drifts in practice - **The helper fills in defaults.** A delete call that passes only a name inherits the application's configured path and domain, which need not be the ones the cookie was created with. - **A different component wrote it.** A cookie issued by the framework's own session, locale or request-forgery machinery carries that component's configured scope, not the application default. - **The write was route-local.** Someone scoped a cookie to a narrower path so it would only ride on part of the site, and the deletion, written elsewhere, used the root path. - **Host versus parent domain.** A cookie written for a parent domain and one written with no domain at all are different stored entries even when both reach the current host; expiring one leaves the other. - **Name constraints.** Some cookie names are only accepted with a fixed scope, so a deletion that deviates from that scope is rejected outright rather than applied. ## Diagnosing it The server cannot see the stored attributes - the inbound `Cookie` header carries only names and values - so diagnosis works from the write side: 1. **Capture the response that creates the cookie.** Use a client that prints every `Set-Cookie` header for the route in question, and read the domain and path off it. 2. **Inspect the stored entry in the browser's storage view**, which shows domain, path, expiry and flags as the client actually kept them. 3. **Compare with the deletion** your logout or reset path emits. Different triple, no effect. 4. **Check for a re-issue.** Another component further along the same response - or a redirect target handled immediately after - may set the cookie again after your deletion. The last write the browser processes wins. ## Other reasons a deletion has no effect | Cause | What you observe | Fix | |---|---|---| | Scope mismatch | Cookie still sent, unchanged value | Expire it with the original domain and path | | Re-issued later in the same response | Cookie present with a fresh value | Order the deletion after the component that writes it, or stop that component running on this route | | Response never delivered as sent | Nothing changes client-side | Check redirects, error handlers and anything that rebuilds the response | | Attributes the client refuses | Deletion header silently ignored | Match the security flags and connection the original required | | Deleted only server-side | Server state cleared, client still holds it | Always pair server-side invalidation with an expiring write | ## Designing so it does not happen Centralise it. Each cookie the application owns should have exactly one place that knows its name and its scope, exposing a write and a delete that share those values; call sites never repeat a path or a domain. Where a cookie must be narrowly scoped, that decision lives with the same helper. Keep the number of distinct scopes small: every additional path or domain variant multiplies the ways a later deletion can miss. Finally, treat deletion as **best effort**. The response can be lost, the user may be offline, the client may be a script that ignores expiry. Anything security-relevant must also be invalidated on the server so that a copy of the cookie that survives is worthless, rather than relying on the client having honoured the expiring write.
- How do you find the domain and path a stubborn cookie was actually stored under?Not from the request - it carries names and values only. Read the browser's cookie storage view, which shows domain, path and expiry as stored, and capture the raw `Set-Cookie` header of the response that creates it. Then find the component or configuration that produced that scope.
- Besides a scope mismatch, what else stops a deletion from taking effect?The response may never reach the client as sent, because a redirect or error path rebuilt it. Another component may re-issue the cookie after the deletion on the same response. The client may refuse the header because its attributes are unacceptable on this connection. And clearing state on the server alone never removes the client's copy.
- Why should security-relevant deletion never rely on the expiring write alone?Because it is a request to the client, not an act on the server. A copy of the value taken before the deletion still exists, and nothing guarantees the client honoured it. Invalidate the underlying state on the server so a surviving copy is worthless, and treat the expiring write as cleanup.
Expiring a cookie is like cancelling a subscription: unless you quote the exact name it was filed under and the exact address it goes to, the clerk cancels nothing and the deliveries keep arriving.
saying these in an interview costs you the question
- Thinks a delete helper removes the cookie from the client store directly
- Deletes by name only and assumes scope does not matter
- Believes clearing server-side state also drops the client's cookie
- Never suspects another component re-issued the cookie on the same response
- Expects the request to reveal the path the cookie was stored under