Using __Host- Prefixed Cookies in Production
Every cookie attribute discussed elsewhere is something your server asserts and the browser accepts. Name prefixes invert that: they make the browser refuse a cookie whose attributes do not match the promise in its name, which turns a convention you hope everyone follows into an invariant the platform enforces. This page is part of the configuring secure cookie flags in production guide.
What the Prefixes Guarantee
Two prefixes exist, and they differ in how much they lock down.
The Attack It Closes
Cookie tossing works because cookies do not respect the same-origin policy the way most web platform features do. Any subdomain of a registrable domain can set a cookie with Domain=example.com, and the main application receives it indistinguishably from one it set itself.
That matters more than it sounds, because subdomains are the part of an estate nobody keeps track of. A marketing site on a third-party platform, a status page, a documentation host, a staging environment, a subdomain pointing at a service that was decommissioned last year and is now claimable by anyone — each is a place from which a cookie can be written into your main application’s jar.
With a __Host- prefix, the browser rejects any attempt to set that cookie with a Domain attribute, so only the exact host can create it. A compromised or hijacked subdomain can still set cookies with other names; it cannot overwrite or shadow the one your authentication depends on.
Migrating an Existing Session Cookie
Renaming the cookie that carries live sessions logs everyone out unless the change is staged. Three steps avoid that.
// During the migration, resolve from either name; issue only the prefixed one.
const LEGACY = "sid";
const CURRENT = "__Host-sid";
export function readSessionId(req: Request) {
const value = req.cookies[CURRENT] ?? req.cookies[LEGACY];
if (value && !req.cookies[CURRENT]) metrics.increment("session.cookie.legacy_name");
return value;
}
When the legacy counter reaches zero for longer than your absolute session lifetime, remove the fallback and clear the old cookie explicitly — with the exact name, path and domain it was originally set with, or the browser keeps it indefinitely.
When You Cannot Use __Host-
The prefix forbids a Domain attribute, which means the cookie does not work across subdomains. If your product genuinely needs one session shared between app.example.com and admin.example.com, the prefix is not available to you.
Before accepting that, check whether the requirement is real. Single sign-on that issues each application its own host-only cookie is usually a better design than one cookie sprayed across a domain: it limits what a compromise of one application reaches, and it makes each application’s session independently revocable. The convenience of a shared cookie is rarely worth the coupling.
Where the requirement stands, use __Secure- with an explicit Domain, and compensate: enumerate the subdomains that can therefore write your cookie, remove the ones you do not need, and monitor for dangling DNS records pointing at decommissioned services. You have accepted a wider trust boundary, so it needs to be a boundary you can actually list.
Verifying It in Production
Local development is the usual place this breaks, because the prefix requires a secure context. Use a locally trusted certificate rather than dropping the prefix in development, or the configuration you test is not the configuration you ship — and the difference will be discovered by a user rather than by a test.
Auditing the Subdomains You Actually Have
Adopting the prefix is a five-minute change; understanding what it protects you from usually is not, because most organisations do not have an accurate list of their subdomains.
Start from DNS rather than from memory. Enumerate every record under your registrable domain, then classify each: which service answers it, who owns that service, and whether it is still in use. The interesting entries are the ones nobody claims — a record pointing at a platform where the account was closed, a host that used to serve a marketing campaign, a staging environment from a project that shipped two years ago. Each of those is potentially claimable by someone else, and each could set cookies on your domain if your session cookie is not host-only.
Then look at what runs on the subdomains you do keep. A documentation site on a third-party platform, an analytics dashboard, a status page, a customer-hosted portal — all are places where content you do not fully control executes on a name inside your cookie boundary. That is not an argument against having them; it is an argument for keeping the session cookie out of their reach.
Record the result somewhere durable and revisit it quarterly, because subdomains accumulate faster than anyone expects and the inventory is only useful while it is current.
Set up monitoring for dangling records so the list stays accurate. Subdomain takeover is a well-understood finding precisely because these records outlive the services behind them, and the difference between a finding and an incident is usually whether the takeover reached a cookie that mattered.
What the Prefix Does Not Solve
It is worth being precise about the boundary, because a control that is trusted beyond its scope is worse than one that is understood.
The prefix stops a subdomain from setting your cookie. It does nothing about a subdomain reading one you deliberately scoped to the parent domain with a Domain attribute — that is the same trust decision it exists to help you avoid making. It also does nothing about script injected into your own origin: an HttpOnly cookie is still unreadable there, but an injected script can still act as the user for as long as the page is open, which is the exposure covered in preventing XSS in auth workflows.
Nor does it affect cross-site sending. A prefixed cookie still travels with cross-site requests according to its SameSite value, so the anti-forgery controls described in the CSRF guide remain necessary. The prefix and SameSite answer different questions: who may create this cookie, and when is it sent.
Used together with HttpOnly, Secure and a sensible SameSite, the prefix completes a small, cheap set of controls that closes the entire family of cookie-level attacks on a session identifier. That is a good return for renaming a cookie.
Frequently Asked Questions
Does the prefix do anything on its own?
Yes — it is enforced by the browser, not by your server. A cookie whose name starts with __Host- is only stored if it also has Secure, has Path=/, and has no Domain. That means a misconfiguration cannot silently ship: the cookie simply is not stored, which fails loudly in testing rather than quietly in production.
Will old browsers break?
Browsers that do not understand the prefix treat it as an ordinary name, so the cookie still works — you simply do not get the guarantee there. Support is universal in current browsers, so in practice you gain the protection everywhere that matters and lose nothing anywhere else.
Can I use it for the anti-CSRF cookie too?
Yes, and you should. The double-submit pattern depends on an attacker being unable to set both halves of the pair, and a __Host- prefixed anti-forgery cookie is exactly what stops a subdomain from doing so. It is one of the clearest cases where the prefix converts a pattern with a known weakness into a solid one.
What if a CDN or platform rewrites my cookies?
Then the cookie the browser receives may not be the one you sent, and a rewritten Path or an added Domain will cause the browser to reject a prefixed cookie outright. Capture the response at the edge rather than in the application, and treat cookie-rewriting rules as security-relevant configuration that needs review like code.
Should every cookie be prefixed?
Every security-relevant one: the session, the anti-forgery token, anything that identifies the user. Ordinary preference cookies gain little and may legitimately need a domain or a path. The rule of thumb is that if forging or shadowing the cookie would help an attacker, it belongs behind a prefix.
For most applications the change amounts to a one-line rename plus a short, staged migration, which is an unusually good ratio of engineering effort to attack surface permanently removed — and it is enforced by the browser rather than by anybody remembering.
Related
- Configuring secure cookie flags in production — the full attribute set and what each one prevents.
- Clearing session cookies that refuse to expire — why the prefix must be reproduced when deleting.
- Implementing double-submit CSRF tokens in React — the pattern the prefix hardens.