This is one of the most thorough, correctly-reasoned writeups of reuse detection I've seen, the family-revocation logic and the atomic rotation transaction are exactly the parts most tutorials skip, and the two subtlety callouts (context on the error path, atomicity) read like real production scars, not textbook advice.
One thing that jumped out: your sessions table already carries ip per session, but from the code shown it's only ever copied forward on rotation, never actually compared against anything. That's a cheap second signal sitting right next to reuse detection. If a refresh request comes from a location that couldn't plausibly be reached from the session's last known IP in the elapsed time, that's worth flagging even when the token itself checks out, since reuse detection only catches theft after the fact, not a session that's still technically valid but now running from somewhere it shouldn't be. IPGeolocation.io's free tier is enough to get lat/long for that comparison if you wanted to add it as an optional check alongside family revocation.
This is one of the most thorough, correctly-reasoned writeups of reuse detection I've seen, the family-revocation logic and the atomic rotation transaction are exactly the parts most tutorials skip, and the two subtlety callouts (context on the error path, atomicity) read like real production scars, not textbook advice.
One thing that jumped out: your sessions table already carries ip per session, but from the code shown it's only ever copied forward on rotation, never actually compared against anything. That's a cheap second signal sitting right next to reuse detection. If a refresh request comes from a location that couldn't plausibly be reached from the session's last known IP in the elapsed time, that's worth flagging even when the token itself checks out, since reuse detection only catches theft after the fact, not a session that's still technically valid but now running from somewhere it shouldn't be. IPGeolocation.io's free tier is enough to get lat/long for that comparison if you wanted to add it as an optional check alongside family revocation.