# cookie-session: Express Sessions That Live Entirely in the Cookie

> cookie-session is an Express middleware that keeps the whole session in a signed client-side cookie, so no database or server-side store is required. The trade-off is stated plainly in its own documentation: the contents are signed but not encrypted, so the client can read what you put in the session.

**expressjs/cookie-session** — Simple cookie-based session middleware

- Repository: https://github.com/expressjs/cookie-session
- Stars: 1,148 · Forks: 209
- Language: JavaScript
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/expressjs-cookie-session

## The Whole Session Is the Cookie

A user session can be stored in two main ways with cookies, and the README opens by making that distinction before introducing the module. The session can live on the server or on the client. This module stores the session data on the client within a cookie, while a module like express-session stores only a session identifier on the client within a cookie and keeps the session data on the server, typically in a database.

The consequences of that choice run through everything else. There is no database and no server-side resource to provision, which removes an entire moving part from a small deployment. The total session data cannot exceed the browser's maximum cookie size, which is the hard ceiling on how much you can put in it. The documentation also points out that cookie-session can simplify certain load-balanced scenarios, since any instance can read the session, and that it can be used to store a light session that includes an identifier for looking up a database-backed secondary store in order to reduce database lookups.

That last pattern is the honest middle ground. Keep the small, non-sensitive part of the session in the cookie and keep the large or sensitive part in a database behind an identifier.

## Signed but Not Encrypted

Two notes at the top of the README define the security boundary, and both deserve to be read before anything else.

The first is that this module does not encrypt the session contents in the cookie, and provides only signing to prevent tampering. The client will be able to read the session data by examining the cookie's value. Secret data should not be set in req.session without encrypting it, or a server-side session should be used instead.

Signing answers the question of whether the client changed the value. It does not answer the question of what is in it. A user who opens developer tools sees a base64 style payload containing your session object, and can read it, even if they cannot forge it.

The second note is about replay. The module does not prevent session replay, because the expiration that is set is that of the cookie only. If that matters for your application, the documentation says you can store an expiration date in the req.session object and validate it on the server, and implement any other logic to extend the session as the application needs.

So the module gives you integrity and confidentiality is your problem. A session holding a display name or a theme preference is fine. A session holding a token, a role or a personal detail is not.

## Wiring the Middleware Into an Express App

The API section gives the whole shape of the usage, and it is a single middleware call:

```js
var cookieSession = require('cookie-session')
var express = require('express')

var app = express()

app.use(cookieSession({
  name: 'session',
  keys: [/* secret keys */],

  // Cookie Options
  maxAge: 24 * 60 * 60 * 1000 // 24 hours
}))
```

The middleware attaches a session property to req, and that session is either a new session if no valid one was provided in the request, or a loaded session from the request. The cookie options are mixed in at the top level of the same object rather than nested, which is worth remembering when you compare it with other middleware.

There is a detail in the documentation that saves a debugging session. The middleware automatically adds a Set-Cookie header to the response if the contents of req.session were altered, and no Set-Cookie header will be in the response, and thus no session created for a specific user, unless there are contents in the session. So you have to add something to req.session as soon as you have identifying information to store for it.

In other words, an empty session produces no cookie at all, which is the correct behaviour and also the reason a session can appear not to work.

## name, keys, secret and Key Rotation

Three options control the identity and the signing of the cookie, and they are worth separating from the cookie options that control how the browser treats it.

name is the name of the cookie to set, and it defaults to session. keys is the list of keys used to sign and verify cookie values, or a configured Keygrip instance. secret is a string used as a single key if keys is not provided.

The keys array has a specific behaviour that makes rotation possible. Set cookies are always signed with keys[0], while the other keys are valid for verification. So you can put the current signing key first and the previous key after it, deploy that, and every cookie signed with the old key still verifies, while every new cookie is signed with the new one. Once enough time has passed for the old cookies to expire, the old key can be dropped.

The Keygrip route exists for the cases where you need more control over the signature itself, since a Keygrip instance can be used to change signature parameters like the algorithm of the signature. That is the option to reach for if your environment has a policy about the signing algorithm rather than just a policy about key hygiene.

## The Cookie Options Decide the Security Posture

Everything else is passed to cookies.get() and cookies.set(), so you control security, domain, path and signing among other settings. The README lists the ones that matter and their defaults, and the defaults are where the sharp edges are.

httpOnly defaults to true, so the cookie is not available to client-side JavaScript, and signed defaults to true. overwrite defaults to true, so previously set cookies of the same name are replaced. path defaults to / and expires defaults to the end of the session, and maxAge is expressed as a number of milliseconds from Date.now().

sameSite defaults to false, and it can be set to strict, lax, none, or true, which maps to strict. That default is worth attention, because a session cookie with no sameSite restriction is sent on cross-site requests, and that is the shape of a cross-site request forgery exposure.

secure defaults to false for HTTP and true for HTTPS, and the README warns that if you set it to true and Node.js is not directly over a TLS connection, you need to read how to set up Express behind proxies or the cookie may never set correctly. The example in the README makes that concrete with app.set('trust proxy', 1) to trust the first proxy.

There is also partitioned, which defaults to false and maps to the CHIPS Update in Chrome, so that cookies from embedded sites are partitioned and only readable from the same top level site that created them, and priority, which takes low, medium or high.

## The Session Object, Its Flags and the Empty Session Rule

req.session represents the session for the given request, and it carries three flags that are useful in middleware and in handlers. isChanged is true if the session has been changed during the request, isNew is true if the session is new, and isPopulated determines whether the session has been populated with data or is empty.

Together they let you avoid the common mistake of writing a cookie on every request. The save happens automatically, but only if there has been a change made to the session when the Node.js response headers are written to the client, and only if the session was not destroyed. Since the entire contents of the session is kept in a client-side cookie, saving is just writing a cookie out in a Set-Cookie response header.

A worked example in the README is a view counter, which increments a number on the session object and therefore marks the session as changed:

```js
app.set('trust proxy', 1) // trust first proxy

app.use(cookieSession({
  name: 'session',
  keys: ['key1', 'key2']
}))

app.get('/', function (req, res, next) {
  // Update views
  req.session.views = (req.session.views || 0) + 1
```

The isPopulated flag exists because of the empty session rule. Since nothing is written when the session has no contents, a handler that wants to know whether the user already has a session can ask the flag rather than inferring it from the presence of a cookie.

## Destroying a Session and Changing Options Per Request

Destroying a session is a single assignment, and the README states it directly: to destroy a session simply set it to null.

```js
req.session = null
```

The save path checks that the session was not destroyed, so a null session is not written back.

Per-request configuration is handled by req.sessionOptions, which represents the session options for the current request. It is a shallow clone of what was provided at middleware construction, and it can be altered to change cookie setting behaviour on a per-request basis. That is the mechanism for, for example, shortening the lifetime of a cookie on a particular route.

The repository itself is small and focused, with a single index.js as the implementation, a test/ directory run with mocha and supertest, a HISTORY.md generated by a script during the version step, and a lint setup using eslint with the standard config. The package depends on cookies at 0.9.1, debug, on-headers and safe-buffer, and declares support for Node.js 0.10 and above.

## Conclusion

cookie-session fits a small Express service that needs a session for a handful of non-sensitive values, or a load-balanced deployment where sharing a session store would add operational weight, and it is the wrong choice the moment the session holds anything secret, because the documentation states that the contents are signed rather than encrypted and that the client can read them. Before adopting it, decide which of the cookie options you actually need, since secure defaults depend on whether the app is behind a proxy and sameSite defaults to false, and remember that the module does not prevent session replay, so an expiration stored inside the session and validated on the server is the piece you have to build. The package is MIT licensed, the newest release is v2.1.1 from 2025-07-17, and the last push was on 2026-08-05.

## FAQ

### Is the session data in cookie-session encrypted?

No. The README states that the module does not encrypt the session contents in the cookie and only provides signing to prevent tampering, so the client can read the session data by examining the cookie's value. Secret data should not be put in req.session without encrypting it, or a server-side session should be used instead.

### How do I install cookie-session?

It is a Node.js module on the npm registry, so `npm install cookie-session` is the whole installation. The current version is 2.1.1 and the package declares support for Node.js 0.10 and above.

### How do I rotate the signing key?

Set cookies are always signed with keys[0] while the other keys in the array remain valid for verification, so you can list the new key first and the old key after it during the rotation window. A Keygrip instance can be supplied instead of the array if you need to change signature parameters such as the algorithm.

### Why is no cookie being set?

The middleware only adds a Set-Cookie header if the contents of req.session were altered, and no session is created unless there are contents in it. Add something to req.session as soon as you have identifying information to store, and check that secure is set correctly when running behind a proxy, since the README notes the cookie may never set correctly in that case.

### Does cookie-session prevent session replay?

No. The README states that the module does not prevent session replay, because the expiration that is set is that of the cookie only. The documented approach is to store an expiration date in the req.session object and validate it on the server, adding whatever logic your application needs.

## Sources

- [expressjs/cookie-session on GitHub](https://github.com/expressjs/cookie-session)
- [Issues](https://github.com/expressjs/cookie-session/issues)
- [License: MIT](https://github.com/expressjs/cookie-session/blob/master/LICENSE)
- [README](https://github.com/expressjs/cookie-session/blob/master/README.md)
- [Releases](https://github.com/expressjs/cookie-session/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/expressjs-cookie-session
