Sessions and authorization

Rotate, revoke, and step up sessions

Renew session identifiers after privilege changes, implement logout, expire idle sessions, and require fresh proof for sensitive actions.

A session is born at login and it should die at some point. One login should not buy unlimited trust forever, so we give sessions a lifecycle: rotate them, expire them, revoke them, and ask for fresh proof before the dangerous actions.

The fields you need

Everything below comes from a few columns in the sessions table:

created_at
last_seen_at
authenticated_at
expires_at
revoked_at

Update last_seen_at at a controlled interval, say once every five minutes, not on every request. Writing on every request turns your session store into a write bottleneck for no security gain.

Two kinds of expiry

Idle expiry closes a session after a period without activity, for example 30 minutes. It handles the laptop left open in a café.

Absolute expiry ends the session a fixed time after login, for example 7 days, even if the user stays active. It caps how long a stolen session stays useful. You want both.

Rotate at every privilege change

Issue a new identifier after login, after a password change, and after any step that raises privileges. Rotation must be atomic: create the new record, invalidate the old one, then send the new cookie.

Test the race here. Fire two requests at the same instant during rotation and confirm the old identifier can’t come back to life. If one request “refreshes” the old session after the other revoked it, an attacker with the old cookie stays in.

Logout, and logout everywhere

Logout sets revoked_at on the current session and expires the cookie. “Log out all devices” revokes every session for the account, including the current one unless the product deliberately keeps it.

Password reset should do the same. If someone stole a session and the user changes their password in response, the stolen session must die too. Revoke everything automatically, or present the choice clearly.

Step-up authentication

Not every action deserves the same proof. Reading a book can use the existing session. Changing the account email, the password, or the recovery options should require fresh proof: a password entered just now, a passkey, or an MFA code.

This is where authenticated_at earns its place. Before a sensitive action, check how long ago the user proved who they are. If it’s more than a few minutes, ask again. A thief with a stolen cookie can read your books, but can’t lock you out.

Try this: add POST /logout and POST /logout-all to the Books API, and a recent-authentication check on the email-change endpoint. Then walk through each transition with a real cookie. After logout, the old cookie returns 401. After logout-all from one browser, a second browser’s cookie returns 401 too. And an email change with a session older than five minutes returns “please re-authenticate”.

Lesson completed