Session cookie flags
From express-session’s documentation: “By default, the Secure attribute is not
set.” The package cannot know whether you are serving over HTTPS, so it declines to
guess — which means the flag is yours to set, and nothing warns if you never do.
app.set("trust proxy", 1); // see the note below — this is not optional
app.use( session({ // ... cookie: { secure: true, // HTTPS only httpOnly: true, // not readable from JavaScript sameSite: "lax", // sent on top-level navigation, not cross-site POSTs maxAge: 1000 * 60 * 60 * 8, }, }));curl -sI https://example.com/login | grep -i set-cookieLook for Secure; HttpOnly; SameSite=Lax on the real response. Reading the config
proves nothing here, because the most common failure produces a config that says
secure: true and a response with no cookie at all.
cookie.secureoptionWhether the session cookie carries Secure. Absent by default. Setting it true behind a proxy without a matching trust proxy value means the cookie is silently never sent — login appears to succeed and the user stays anonymous.
- accepts
true|false|"auto"- default
not set — the Secure attribute is absent- set in
session() options
cookie.httpOnlyoptionAlready on, which is correct — the session cookie is a credential.
- accepts
true|false- default
true- set in
session() options
cookie.pathoptionCookie path scope.
- accepts
<path>- default
"/"- set in
session() options
cookie.maxAgeoptionNull by default, so the cookie expires when the browser closes rather than on a fixed schedule.
- accepts
<ms>|null- default
null (browser-session cookie)- set in
session() options
Warning: connect.session() MemoryStore is not designed for a production environment, as it will leak memory, and will not scale past a single process.No session store was configured, so sessions live in the process and vanish on restart. The catch is when it appears: express-session only prints this when NODE_ENV is production, so it is absent during the development that preceded the deploy. It is emitted wrapped across three lines.
Read in: express-session index.js — reproduced on express-session 1.19.0 with NODE_ENV=productionWhy secure: true alone can log everyone out
Section titled “Why secure: true alone can log everyone out”This is the trap, and express-session’s docs call it out directly:
If
secureis set, and you access your site over HTTP, the cookie will not be set. If you have your node.js behind a proxy and are usingsecure: true, you need to set “trust proxy” in express.
Behind a TLS-terminating proxy, Express receives plain HTTP. With secure: true and
trust proxy unset, express-session decides the connection is insecure and declines
to send the cookie at all. There is no error. Login appears to succeed and the user
is immediately anonymous again.
app.set("trust proxy", 1) tells Express to believe X-Forwarded-Proto from the
first hop. It is the same one-line-either-side problem as Django’s
SECURE_PROXY_SSL_HEADER, and it comes with the same caveat — the proxy must strip
that header from client requests, or anyone can assert it. That is on
trust proxy.
secure: true breaks local development over http://localhost for exactly the reason
above. Gate it on the environment rather than hardcoding it:
const isProd = process.env.NODE_ENV === "production";cookie: { secure: isProd, httpOnly: true, sameSite: "lax" }sameSite: "strict" withholds the cookie even on a top-level navigation from another
site, so a user following a link from an email arrives logged out. lax is the right
default for almost every application.
Related
Section titled “Related”- trust proxy — required for
secure: truebehind a proxy - Session secret and store — what signs this cookie
- Sessions: signed, not encrypted — why this cookie holds an id where Flask and FastAPI hold the data