Refresh Tokens Need Families, Not Just Expiry
Short-lived access tokens are easy to explain. Refresh tokens are where security actually lives — and where most systems under-design.
A naive refresh token is a long-lived secret: present it, get a new access token. If it leaks, the attacker looks like a legitimate client until someone notices.
The fix is not “make refresh shorter and hope.” The fix is rotation with family tracking.
Rotation in one picture
Both device sessions and user account sessions follow this pattern: each refresh token row has a family id, optional replaces pointer, expiry, and revoke metadata. The session aggregate keeps a pointer to the current refresh token.
Why “family” instead of one mutable secret
| Approach | What breaks |
|---|---|
| Single mutable refresh in DB | Race between tabs; no theft signal |
| Stateless refresh JWT only | Hard to revoke; reuse invisible |
| Rotate without family | You can revoke one token, miss the lineage |
| Rotate with family | Reuse of any ancestor → kill the whole chain |
A family is the lineage of rotations that started from one login (or one device bootstrap). Compromise reason in the domain is explicit: token reuse, admin action, or suspicious signals.
What reuse detection means operationally
When a revoked (or already-replaced) refresh token is presented again:
- Emit a reuse-detected domain event
- Mark the session compromised
- Invalidate the family — not just the single row
That turns a silent leak into a loud, local failure: that device / that login dies. Other devices on other families can survive.
Device and account both get families
It is tempting to apply families only to “the login token.” In a two-layer model, device refresh and account refresh are both high-value secrets.
- Device family theft → that client is no longer trusted; account sessions riding it should not get a free pass
- Account family theft → that login on that device is dead; other devices remain
The frontend must refresh device first, then account with the effective device access token. Wrong order creates false failures that look like bugs and train you to weaken security.
Parallel requests are the hidden enemy
Browsers fire HTML, RSC, and prefetch in parallel. Each sees an expired cookie and each tries to refresh the same token.
Without coordination you get:
- Benign reuse (your own race) treated as theft, or
- Backend stampedes and flaky UX
Identity still owns rotation correctness. The edge owns single-flight so one process does not stampede itself. Those are complementary layers — chapter 9 covers the edge side.
Takeaway
Expiry limits damage. Families detect theft.
If your refresh story is only “long-lived JWT until logout,” you have a secret, not a session security model.
Next
OTP, password, and OAuth look different at the door. Inside the domain they should converge on one session-establishment path.
Continue with Authentication Convergence.