Context
Refresh tokens are currently rotated on each use: presenting a token revokes it and issues a new pair (ADR 0002). However, if an attacker steals a refresh token before the legitimate user rotates it, both parties hold a valid token and the system has no way to detect the compromise.
Goal
Detect refresh-token replay and revoke the entire token chain when it happens.
Approach
- Add a
chainId (UUID) field to RefreshToken. All tokens issued from a single login share the same chainId.
- On
/auth/refresh, if a presented token is already revoked, treat it as replay: revoke every token in its chain (revokedAt = now) and return 401.
- Log the event with the user id and request id for audit.
Acceptance criteria
Out of scope
User-facing notification ("you may have been hacked"). Out of scope for this iteration.
Context
Refresh tokens are currently rotated on each use: presenting a token revokes it and issues a new pair (ADR 0002). However, if an attacker steals a refresh token before the legitimate user rotates it, both parties hold a valid token and the system has no way to detect the compromise.
Goal
Detect refresh-token replay and revoke the entire token chain when it happens.
Approach
chainId(UUID) field toRefreshToken. All tokens issued from a single login share the same chainId./auth/refresh, if a presented token is already revoked, treat it as replay: revoke every token in its chain (revokedAt = now) and return 401.Acceptance criteria
chainIdcolumn + migrationOut of scope
User-facing notification ("you may have been hacked"). Out of scope for this iteration.