Cookie flags (HttpOnly, Secure, SameSite)
The attributes on a Set-Cookie header that decide who can read it, whether it travels unencrypted, and which sites can send it back.
See it
What it is
These are the attributes tacked onto a Set-Cookie header, and each one closes a specific hole. HttpOnly hides the cookie from document.cookie, so an injected script cannot read the session out of the page. Secure means the cookie only travels over HTTPS. SameSite decides cross-site sending: Lax (sent on top-level navigations, the browser default now), Strict (never sent from another site, which breaks the 'click the link in the email and land logged in' flow), None (sent everywhere, only legal alongside Secure). Around them sit Domain and Path for scope, Max-Age or Expires for lifetime, and the __Host- name prefix that forces Secure, Path=/, and no Domain.
For a session cookie the default answer is HttpOnly, Secure, SameSite=Lax, Path=/, and no Domain unless subdomains genuinely need it. Reach for SameSite=None; Secure only when your app is embedded in an iframe on someone else's origin, or when a redirect flow POSTs back to you from another site. Anything a client script must read (a theme preference, a feature flag) belongs in a different, non-sensitive cookie.
Gotcha: a large share of 'it works locally but breaks in production' auth bugs are cookie attributes. Secure cookies will not set over plain http, and while browsers special-case localhost, a staging box on a raw IP is not special-cased. Setting Domain=example.com hands the cookie to every subdomain, including whatever user-content subdomain you add later. And SameSite=Lax quietly drops the cookie on cross-site POST callbacks, which is why some SAML and OAuth form_post logins fail only in production.
Ask AI for it
Harden every authentication cookie this app sets. Make the session cookie host-only with the __Host- prefix, which means Secure, HttpOnly, Path=/, and no Domain attribute at all: those requirements are part of the prefix, so do not add a Domain later and expect it to still work. If cross-subdomain authentication is genuinely required, do not weaken this cookie. Design a separate domain-scoped cookie flow without the __Host- prefix, and document the wider trust boundary it creates, including every subdomain that now receives the credential. Set Max-Age to match the server-side session lifetime exactly so the two cannot disagree, and clear cookies on logout by setting the same name with the same Path, the same Domain if one was used at all, and Max-Age=0. Keep Secure on in every environment, running local development over HTTPS or on localhost rather than turning the flag off. If any flow needs SameSite=None, document why, apply it only to that cookie, and pair it with CSRF protection. Move anything client-side JavaScript must read into a separate non-sensitive cookie. Then add a test that asserts the exact Set-Cookie attribute string on the login response.