Refresh-token rotation is often described as “issue a new token and invalidate the old one.” Its security value comes from the history retained between them.
If both a legitimate client and an attacker possess one refresh token, one will eventually present a token that has already been consumed. The authorization server detects replay and revokes the active token family. Rotation converts otherwise invisible credential theft into an observable conflict.
Store a token family, not isolated strings #
Persist only a cryptographic hash of each opaque token. Associate tokens with one authorization grant and a generation:
CREATE TABLE refresh_token (
token_hash bytea PRIMARY KEY,
family_id uuid NOT NULL,
generation integer NOT NULL,
client_id text NOT NULL,
subject_id text NOT NULL,
status text NOT NULL,
expires_at timestamptz NOT NULL,
consumed_at timestamptz,
replaced_by_hash bytea,
UNIQUE (family_id, generation)
);The family lets the server revoke the currently active descendant when an ancestor is replayed. The client binding, scope, and resource-server audience must remain constrained to the original grant.
Rotation must be atomic #
Two browser tabs or mobile requests can refresh concurrently. A naive read-then-write flow may allow both to observe the same active token and mint two children.
Use one transaction and lock the presented token row:
BEGIN
load token FOR UPDATE
verify client, expiry, grant, status
if ACTIVE:
mark CONSUMED
insert generation + 1
link replacement
COMMIT and return new pair
if CONSUMED:
revoke family
COMMIT and rejectThe access token and replacement refresh token should be derived only after all policy checks. If the transaction fails, do not return credentials the database did not record.
Handle lost responses deliberately #
The authorization server can commit rotation while the response is lost. The legitimate client still holds the consumed parent and retries; strict reuse detection then revokes its own family.
There are three defensible approaches:
| Policy | Benefit | Cost |
|---|---|---|
| strict one-time use | strongest immediate replay signal | lost response forces login |
| very short retry grace returning same child | tolerates network retry | requires safely recoverable response/token material |
| sender-constrained tokens | stolen value alone is insufficient | key management and client support |
RFC 9700 requires public clients receiving refresh tokens to use rotation or sender-constraining. DPoP and mutual TLS are standardized ways to bind tokens to key possession in applicable deployments.
A grace window must not mint multiple children. It should recognize the same client instance and return the already committed outcome, or deliberately accept the reauthentication cost.
Security response #
On confirmed reuse, revoke the active family, emit a security event, invalidate relevant server-side sessions where policy requires it, and require a new authorization grant. Do not attempt to guess whether the first or second presenter was the attacker.
Revoke on password change, logout, client compromise, or other high-confidence security events according to product policy. Apply inactivity and maximum lifetimes so continuous rotation does not create an immortal credential.
Observability without leakage #
Log family and generation identifiers, client ID, policy outcome, and coarse risk signals. Never log raw tokens. Monitor reuse rate, concurrent refresh conflicts, family revocations, refresh latency, and login recovery rate. Sudden reuse spikes may indicate a client concurrency bug rather than a new attacker campaign; the response must still preserve security.
Common mistakes #
- Storing refresh tokens in plaintext.
- Invalidating the parent without retaining family history.
- Performing rotation outside one transaction.
- Extending the absolute grant lifetime on every refresh.
- Putting bearer tokens in URLs or analytics logs.
- Treating a device label as cryptographic sender constraint.
Trade-offs #
Strict rotation detects replay but introduces a distributed race between client persistence and server commit. Retry grace improves availability while expanding state and replay analysis. Sender-constrained tokens reduce the value of theft, but require protected client keys.
Rotation is not token housekeeping. It is a replay-detection protocol whose failure behavior must be designed before the happy path.