Signed Cookies

Signed Cookies and Cookie Attributes

A cookie is client-side storage, so the client can edit it. Signing does not hide the value — it proves your server wrote it. Pass a secret to cookieParser and set signed: true: Express 24,430 appends an HMAC-SHA-256 of the value (Hashes and HMACs), which cookie-parser 2,030 verifies before putting the result on req.signedCookies.

A signed, HttpOnly cookieJavaScript
app.get('/set', (req, res) => {
  res.cookie('uid', 'u_1042', { signed: true, httpOnly: true, sameSite: 'strict' });
  res.send('ok');
});
app.get('/read', (req, res) =>
  res.json({ cookies: req.cookies, signed: req.signedCookies }));
Output
Set-Cookie: uid=s%3Au_1042.Uo3%2BJOlPdoz2NKVtHxafmOH7GF3ns4lnO63fZV%2Fxjak; Path=/; HttpOnly;
            SameSite=Strict
$ curl -s -b jar.txt .../read  ->  {"cookies":{"theme":"dark"},"signed":{"uid":"u_1042"}}
$ curl -s -H 'Cookie: uid=s%3Au_9999.Uo3%2BJOlPdoz2NKVtHxafmOH7GF3ns4l...' .../read
{"cookies":{"theme":"dark"},"signed":{"uid":false}}

Changing u_1042 to u_9999 leaves the signature no longer matching, and the value arrives as false — not as a string, and not as an exception, so check for truthiness before trusting it. An array (cookieParser(['new', 'old'])) rotates keys: cookie-parser signs with the first and accepts any of them.

Cookie attributes that matter for authentication
Attribute Effect Use it for
httpOnly JavaScript cannot read the cookie every auth cookie
secure sent over HTTPS only every auth cookie in production
sameSite: 'lax' suppressed on cross-site subrequests the sensible default
sameSite: 'strict' suppressed on all cross-site navigation admin panels
sameSite: 'none' always sent; requires secure cross-origin SPA and API

httpOnly turns a stolen-cookie bug into a non-event: an XSS payload can still make requests as the user, but it cannot read the session ID and mail it somewhere. sameSite: 'lax' blocks the classic CSRF form post, but not same-site subdomain attacks, so keep the defenses of CSRF Protection After csurf too. And secure: true over plain HTTP in development silently drops the cookie, so drive it from the environment.