Refresh tokens rotate on every use, reuse of a spent token revokes the entire token family, and access tokens issued after detection are invalid, following RFC 6749 grant rules and OAuth 2.0 Security BCP reuse guidance.
Package status: reference context ready for human review. The contract and test scenarios are complete, but no claim is made that an adopting implementation has passed them.
Decision
Confidential OAuth clients receive rotating refresh tokens. Each successful refresh grant spends the presented token, issues a new refresh token in the same family, and issues a new access token. If a spent refresh token is presented again, the authorization server treats it as reuse: the entire refresh family is revoked, related access tokens are denied, and the response is invalid_grant as specified by RFC 6749. RFC 9700 describes rotation and reuse detection as current practice for stolen-token races. Interactive re-authorization is required after family revoke.
Scope
- Refresh token grant handling for oauth-clients using confidential client authentication.
- Rotation, spent-token detection, family revoke, access-token invalidation, and concurrent refresh races.
Outside this block
- Authorization-code issuance and PKCE, public native-client storage, and resource-server JWT signature checks beyond revocation.
- Session cookie format (related session-lifecycle package).
Contract
- A successful refresh spends the presented refresh token before or in the same commit that persists the successor.
- The successor belongs to the same family_id until reuse or logout revokes the family.
- Presenting a spent refresh token returns invalid_grant and revokes all family refresh digests.
- Access tokens from a revoked family fail at the resource server within the denylist or introspection path chosen in the profile.
- Concurrent refreshes cannot produce two surviving current tokens for the same family.
- Error responses do not distinguish theft from client bugs.
Implementation guidance
- Store only refresh digests; the raw token is shown once. Serialize family updates with a row version. Prefer sender-constrained tokens as defense in depth; they do not replace reuse detection.
- Follow reuse-playbook.md for concurrency serialization and paging after reuse-detected events.
Failure handling and safeguards
- If successor persist fails after spend, the family is unusable; the client must re-authorize rather than resurrect the spent token.
- After a timeout, the client must present the newest refresh it persisted; presenting the old one trips reuse.
Verification and operations
- Rotate twice and prove the first token is spent. Reuse the first token and prove family revoke plus access denial. Race two refreshes and assert a single head.
The executable-looking examples in this package are fixtures and acceptance contracts. Run
python tools/validate.py from the collection root to check package structure and metadata; then
implement and execute the scenarios in the target repository.
Adoption assumptions
- Names and numeric values in
example.yamlare an adoption profile, not universal defaults. - The adopting team must map actors, data classes, error vocabulary, and ownership to its system.