skip to content

A server-side session runs on an idle clock and an absolute clock — what does each one defend against?

level: juniorimportance: must knowfreq 74%

answer

  1. two timers, not one
  2. one restarts on activity, one never does
  3. unattended console versus an ageing sign-in
  4. sliding the idle clock means a write
  5. the cookie's own lifetime is the client's

basics

~20 s

The idle clock restarts on every request and ends a session left unattended. The absolute clock counts from the sign-in that created the record, never restarts, and caps how long one authentication may keep counting.

solid answer

~40 s

A stored session is ended by two independent timers. The **idle clock** measures the time since the last request that reached the server and restarts on every one; it exists so a console someone walked away from stops being usable. The **absolute clock** measures the time since the authentication that created the record and does not restart, however busy the operator is; it caps how long a single proof of identity keeps buying access. The session ends when either fires, and the two numbers are chosen separately, because they price different risks. Sliding the idle clock is not free: to restart it the server has to write the record's `lastSeenAt` back to the session tier on every authenticated request, which is why a session lookup is not a pure read on the hot path.

code

pseudocode · 24 lines
pseudocode
IDLE_MAX     = 45 minutes      # restarts on activity
ABSOLUTE_MAX = 3 hours         # counts from the authentication
WRITE_AFTER  = IDLE_MAX / 20   # how stale lastSeenAt may get

on every authenticated request:
    record = sessionStore.get(sessionId)
    if record == null:
        reject(401)                                   # nothing to authenticate

    now = clock.now()

    if now - record.createdAt >= ABSOLUTE_MAX:
        sessionStore.delete(sessionId)
        reject(401)

    if now - record.lastSeenAt >= IDLE_MAX:
        sessionStore.delete(sessionId)
        reject(401)

    if now - record.lastSeenAt >= WRITE_AFTER:        # bounded sliding expiry
        record.lastSeenAt = now
        sessionStore.put(sessionId, record, ttl = IDLE_MAX)

    proceed(record.principal)

go deeper

for a junior

Recall that a stored session dies on more than one timer: one counts silence since the last request, the other counts from sign-in whatever the user is doing. Naming both, and saying which restarts, is the answer.

for a middle

Explain what each timer is measured from, that the server compares them on every request, and that the record on the server — not the cookie on the client — is the thing the server can actually enforce.

for a senior

Show you have priced sliding expiry: a restarting idle clock writes to the session tier on every authenticated request, and you bound that by only writing once the stored last-seen value is stale enough to matter.

for a principal

Argue both numbers from the cost of being wrong in each direction — a terminal left usable in a public hall against an operator challenged in the middle of a timed task — instead of quoting a default.

## Why one number cannot do the job A server-side session is a **record the server keeps** plus a **reference it hands the client**. When that record stops authenticating is a policy decision nothing hands you: HTTP's own challenge-response framework (RFC 9110) describes credentials presented on each request, and a cookie-carried session sits outside it, so there is no standard field called "session lifetime" to copy. The server author chooses, and the choice has two halves because two different risks are running at once. - **An unattended client.** An invigilation console sits in a hall while its operator walks the room for forty minutes. The exposure grows with *silence*, so the defence has to measure silence. - **An ageing authentication.** Whoever signed in proved who they were once, at the start of a three-hour sitting. The exposure grows with *wall-clock time since that proof*, whatever the operator is doing, and a busy session is no safer than a quiet one on this axis. Measure only silence and a continuously-used session never ends. Measure only elapsed time and an abandoned console stays usable until the sitting's hard boundary. Hence two clocks. ## What each clock is measured from | | Idle clock | Absolute clock | |---|---|---| | Measured from | the last request that reached the server | the authentication that created the record | | Restarts on activity | yes | no | | Defends against | a usable session on a client nobody is watching | one sign-in that keeps counting long past its useful life | | Usual shape | minutes to tens of minutes | hours, often aligned to a real-world boundary | | Ongoing cost | a store write per request, unless bounded | none after the record is created | Enforcement is a comparison the server runs on **every** authenticated request, before it does anything else with the record: `now - lastSeenAt` against the idle limit, `now - createdAt` against the absolute limit. Whichever fires first ends the session. The comparison is what enforces the policy — a record whose row happens to still exist because a cleanup job has not run must not be allowed to authenticate. ## Sliding expiry is a write, not a read An idle clock that restarts on activity means the server must **store** the new activity time. That turns each authenticated request from a lookup into a lookup plus a write against the session tier, and on a busy service that write, not the read, is what sizes the tier. Candidates almost always miss this and describe the session store as read-mostly. The standard way to bound it is to make the last-seen value deliberately coarse: only write it back when the stored value is already stale by more than a fraction of the idle window. 1. Read the record and run both comparisons. 2. If `now - lastSeenAt` is smaller than the chosen fraction of the window, skip the write entirely. 3. Otherwise write the fresh value and let the request continue. The honest cost is that the idle window now ends up to that fraction **short** — a twentieth-of-the-window threshold means a session can expire about 5% early — and in exchange the write rate is capped by the threshold rather than by the request rate. The absolute clock needs no such trick: `createdAt` is written once and never changes. ## Choosing the two numbers These are judgment calls, not defaults to quote. Three questions decide them: 1. **What does an idle session on this client expose?** A terminal in a hall that several people pass is not a laptop in a locked office, and the idle number should say so. 2. **What does a challenge cost the user mid-task?** An operator asked to re-authenticate in the middle of a timed paper is a real operational failure, which is why the idle window has to survive a long walk around the room. 3. **Is there an external boundary?** When work genuinely ends — a sitting finishes, a shift changes — the absolute clock should land on or before it, so nothing carries into the next cohort by accident. ## The clocks the server does not own At least two other timers are in play and they routinely disagree with the policy: - **The cookie's own `Max-Age` or `Expires`.** The client enforces these and the server cannot verify that it did. A `Set-Cookie` with neither attribute lasts until the browser process ends, which is a courtesy rather than a control, and a value copied out of the cookie jar ignores both. - **The store's row lifetime.** A time-to-live on the stored record bounds growth and cleans up after abandoned sessions. It should be at least as long as the window it backs, and it is housekeeping, not policy. When someone reports that "the session lasted longer than we configured", the first question is which of these four values they configured. ## What expiry is not Expiry ends **one record** at a moment nobody chose. It is not the same event as a user pressing sign-out, which happens at a chosen moment and drags a list of other things down with it, and it is not a way to end every session a person has. Those are separate mechanisms with separate failure modes.

  • Why is a session lookup with a sliding idle clock not a read-only operation?
    Restarting the idle clock means recording that a request arrived, so the last-seen value has to be written back to the session tier. On a busy service that write, not the read, is what sizes the tier. Writing only once the stored value is stale by a fixed fraction of the window caps the rate, at the cost of an idle window that ends slightly early.
  • A session cookie carries no Max-Age and the stored record has a three-hour absolute clock. Which one ends the operator's access?
    The record does. Without `Max-Age` or `Expires` the browser drops its copy when the process ends, which is a client-side courtesy the server cannot verify and a copied value ignores. Access ends when the server's own comparison fails, so the server must run it on every request rather than trusting that the reference has gone.
  • Does an expired record need to be deleted, or is checking the timestamps enough?
    The check is what enforces the policy — a row that still exists must not authenticate. Deletion does two other jobs: it bounds the growth of the session tier, and it removes data that no longer needs to exist. A store-side time-to-live handles the housekeeping; it should be at least as long as the window it backs.

An exam hall ends a sitting two ways. An invigilator's patience with a desk where nothing is happening can be reset — someone picks up a pen and the countdown starts over. The clock on the wall cannot: it stops the paper at three hours however hard everyone is writing. The idle clock is the first rule, the absolute clock is the second.

saying these in an interview costs you the question

  • Names a single session timeout and stops there.
  • Thinks the cookie's Max-Age enforces the server's session lifetime.
  • Says sliding the idle window costs the session tier nothing.
  • Restarts the absolute clock on activity, so it never fires.
  • Treats clearing the browser's cookie as ending the stored record.