Sessions and how long people stay logged in
When someone logs in, the site remembers them with a session, so they do not log in on every page. This page is for whoever runs the server. Most sites never change the defaults: the database on Node and Docker, KV on Cloudflare. Change them when you run more than one copy of the site, or when you want logins to last a different time.
How long people stay logged in
| Variable | Default | What it sets |
|---|---|---|
ADMIN_SESSION_HOURS | 24 | How long an admin stays logged in, in hours, counted from the moment they log in. |
MEMBER_SESSION_DAYS | 30 | How long a member stays logged in, in days. Members log in with an emailed link, so this is long on purpose. |
A value that is not a whole number is ignored and the default is used. Changing either affects people who log in afterwards; a session already open keeps the lifetime it was given.
Whatever the lifetime, access ends sooner in two cases, checked on every request:
- an admin who is disabled or deleted (when each admin has an account) is logged out at once;
- a member who is disabled or deleted is logged out at once.
Admins and members are separate. One browser can be logged in as both, and logging out of one does not log out of the other.
Where sessions are kept
SESSION_STORE chooses.
| Value | Where | Use it when |
|---|---|---|
database | A table called sessions in the site's own database. Expired rows are cleared out now and then. | The default on Node and Docker. No extra service, and it works with several copies of the site sharing one database. |
redis | A Redis server (or Valkey, KeyDB or another that speaks the same protocol), at REDIS_URL. Each session is a key beginning session: that expires on its own. | You already run Redis, or want session traffic off the database. Node only. |
memory | The server's own memory. | Development only. Every login is lost at each restart, and it cannot work with more than one process. In production the server logs a warning. |
kv | A Cloudflare KV namespace, bound as SESSIONS. | The default on Cloudflare Workers. |
Any other value stops the server at start on Node with Unknown SESSION_STORE "…". Use database, redis or memory (kv is for Cloudflare). On Node, kv gives the same message.
Database
Nothing to configure. The table is created with the rest of the schema (How the database schema is kept up to date).
Redis
SESSION_STORE=redis
REDIS_URL=redis://redis:6379
REDIS_URL is the address of the server, in the form redis://[user:password@]host:port. Without it the server logs SESSION_STORE=redis needs REDIS_URL. Redis only ever holds sessions, which are small. If Redis cannot be reached, the log says [sessions] redis error: followed by the reason, and people cannot log in until it is back. Docker Compose users: docker-compose.yml has a commented-out Redis service you can switch on.
Sessions are lost if Redis loses its data. Everyone then logs in again; nothing else is harmed.
Cloudflare KV
On Workers the store is kv unless you say otherwise, and it needs this in wrangler.toml:
[[kv_namespaces]]
binding = "SESSIONS"
id = "YOUR_KV_NAMESPACE_ID"
Create the namespace with npx wrangler kv namespace create SESSIONS and copy the id it prints. Two ways to do without KV, both using D1 instead: set SESSION_STORE to database, or leave the binding out. On Workers, if SESSIONS is not bound, the site uses D1 whatever SESSION_STORE says.
The cookies
The site sets two cookies, one for each kind of login:
| Cookie | For |
|---|---|
cm_admin_session | Admins |
cm_member_session | Members |
Each holds only a random 64-character code. Nothing about the person is in it, and it is not signed: the code means something only to the session store. Each cookie is HttpOnly (scripts on the page cannot read it), SameSite=Lax, for the whole site, and has no Domain, so it is sent only to the exact host name. Max-Age equals the session's lifetime.
It is also Secure (sent over HTTPS only) whenever NODE_ENV is production, which is the default. That is why a production site served over plain HTTP, for instance when you test by address on your own network, cannot keep a login: most browsers refuse a Secure cookie over plain HTTP. Put the site behind HTTPS, or set NODE_ENV=development for a local trial only.
Log everyone out
There is no button for this in the admin panel. Empty the session store instead; everyone, you included, is asked to log in again, and nothing else changes. Ending a single person's access is easier: disable or delete them.
| Store | How |
|---|---|
database on Node | Delete every row of the sessions table. For SQLite, with the site stopped or running: node -e "const {DatabaseSync}=require('node:sqlite');new DatabaseSync('./data/choir.sqlite').exec('DELETE FROM sessions')" from the site's folder (change the path if you set SQLITE_PATH). For PostgreSQL or MySQL run DELETE FROM sessions; in psql or mysql. |
database on D1 | npx wrangler d1 execute choir-db --remote --command "DELETE FROM sessions" |
redis | Delete the keys that begin session:. Untested here: redis-cli --scan --pattern 'session:*' | xargs redis-cli del. Avoid FLUSHALL if the Redis server holds anything else. |
memory | Restart the server. |
kv | Point wrangler.toml at a new, empty KV namespace and deploy again. Untested here; it follows from the way the store works, because a session is found only in the namespace it was written to. |
The SQLite command was run against a copy of the table. The other rows follow the code and were not run.
Changing ADMIN_PASSWORD does not log anyone out: a session does not record the password.
If something goes wrong
| What you see | Cause and fix |
|---|---|
| Logging in works, but the next page asks again | The cookie is not being kept. Usually a production site served over plain HTTP (see above). |
| Everyone is logged out after each restart | SESSION_STORE=memory. Use database. |
| Logged in on one copy of the site, logged out on another | memory with more than one process. Use database or redis. |
[sessions] redis error: … in the log | Redis is down or REDIS_URL is wrong. |
SESSION_STORE=memory loses every login on restart and does not work with more than one process at start | The warning for a production site on memory. Not fatal. |