Skip to content

Add server-side session inactivity timeout - #513

Open
parndt wants to merge 4 commits into
sg-devfrom
session/inactivity-timeout
Open

Add server-side session inactivity timeout#513
parndt wants to merge 4 commits into
sg-devfrom
session/inactivity-timeout

Conversation

@parndt

@parndt parndt commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Enforces a per-token inactivity timeout for devise_token_auth sessions,
addressing the pentest finding that sessions stay valid after >30 min idle.

Token rotation is off (change_headers_on_each_request = false) to avoid
batch write-contention, so DTA's native sliding expiry is unavailable.
Instead:

  • token_activities table stores (user_id, client_id, last_activity_at),
    one row per live token. It lives off the users row and outside PaperTrail
    so activity writes don't reintroduce contention or version noise.
  • TokenActivityEnforcement is a read-only before_action that rejects any
    request whose token row is stale or missing (fail closed), deleting the
    stale row rather than writing to users.tokens on the hot path.
  • POST /auth/activity is a client heartbeat, the only writer, coalesced so
    the write rate stays ~1 per token per window. Enforcement runs first, so a
    late ping cannot resurrect an already-expired session.
  • User#reconcile_token_activities (after_save) keeps the row set in step with
    the tokens hash, covering login, sign-out, password-reset pruning and
    max_number_of_devices eviction in one seam.
  • Thresholds live in config/initializers/session_timeout.rb (30 min timeout,
    2 min coalesce window). Existing tokens are backfilled at deploy so no live
    session is force-expired.
  • The password-change prune (remove_tokens_after_password_reset) is a
    before_save, so User#reconcile_token_activities - an after_save - sees the
    already-pruned tokens hash and drops the activity rows for the invalidated
    sessions in the same transaction. This pins that projection, and makes the
    forward-referencing comments already in this spec true.

The client heartbeat (interaction-gated ping to /auth/activity) is still to
be wired up in the SPA.

tmfrnz and others added 4 commits July 16, 2026 10:47
Enforces a per-token inactivity timeout for devise_token_auth sessions,
addressing the pentest finding that sessions stay valid after >30 min idle.

Token rotation is off (change_headers_on_each_request = false) to avoid
batch write-contention, so DTA's native sliding expiry is unavailable.
Instead:

- token_activities table stores (user_id, client_id, last_activity_at),
  one row per live token. It lives off the users row and outside PaperTrail
  so activity writes don't reintroduce contention or version noise.
- TokenActivityEnforcement is a read-only before_action that rejects any
  request whose token row is stale or missing (fail closed), deleting the
  stale row rather than writing to users.tokens on the hot path.
- POST /auth/activity is a client heartbeat, the only writer, coalesced so
  the write rate stays ~1 per token per window. Enforcement runs first, so a
  late ping cannot resurrect an already-expired session.
- User#reconcile_token_activities (after_save) keeps the row set in step with
  the tokens hash, covering login, sign-out, password-reset pruning and
  max_number_of_devices eviction in one seam.
- Thresholds live in config/initializers/session_timeout.rb (30 min timeout,
  2 min coalesce window). Existing tokens are backfilled at deploy so no live
  session is force-expired.

The client heartbeat (interaction-gated ping to /auth/activity) is still to
be wired up in the SPA.
The password-change prune (remove_tokens_after_password_reset) is a
before_save, so User#reconcile_token_activities - an after_save - sees the
already-pruned tokens hash and drops the activity rows for the invalidated
sessions in the same transaction.

Pins that projection, and makes the forward-referencing comments already in
this spec true.
@parndt
parndt requested a review from tmfrnz July 21, 2026 02:36
Base automatically changed from sg-dev-invalidate-sessions-on-pw-change to sg-dev July 21, 2026 07:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants