feat(oauth): add refresh token rotation replay detection (RFC 6819)#104
Merged
feat(oauth): add refresh token rotation replay detection (RFC 6819)#104
Conversation
- Add RevokeTokenFamily store method to revoke all tokens in a token family - Detect reuse of revoked/disabled refresh tokens in rotation mode and revoke entire family - Log token family revocation as CRITICAL audit event for security monitoring - Add store and service tests for replay detection and fixed mode behavior Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Contributor
There was a problem hiding this comment.
Pull request overview
Adds refresh token rotation replay detection and token-family revocation, aligning refresh-token security behavior with RFC 6819 guidance and expanding tests around rotation vs fixed refresh modes.
Changes:
- Introduces a
Store.RevokeTokenFamily(parentTokenID)method to bulk-revoke tokens in a “family”. - Adds rotation-mode replay detection in
TokenService.RefreshAccessToken, revoking a token family and writing a CRITICAL audit event when a revoked/disabled refresh token is reused. - Adds store + service tests covering family revocation and rotation/fixed-mode behavior.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| internal/store/sqlite.go | Adds RevokeTokenFamily DB update for revoking active tokens in a family. |
| internal/services/token.go | Adds family-revocation helper with audit logging and hooks it into refresh-token validation. |
| internal/store/store_test.go | Adds integration test validating RevokeTokenFamily revokes the expected tokens only. |
| internal/services/token_test.go | Adds tests for rotation-mode replay detection and fixed/rotation behavior on disabled-token reuse. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
internal/store/sqlite.go
Outdated
Comment on lines
+670
to
+677
| // RevokeTokenFamily revokes all tokens that share the same ParentTokenID (token family). | ||
| // This is used for refresh token rotation replay detection: when a revoked refresh token | ||
| // is reused, all tokens in the family must be invalidated to prevent stolen token abuse. | ||
| func (s *Store) RevokeTokenFamily(parentTokenID string) (int64, error) { | ||
| result := s.db.Model(&models.AccessToken{}). | ||
| Where("(parent_token_id = ? OR id = ?) AND status = ?", parentTokenID, parentTokenID, models.TokenStatusActive). | ||
| Update("status", models.TokenStatusRevoked) | ||
| return result.RowsAffected, result.Error |
internal/services/token.go
Outdated
Comment on lines
+470
to
+476
| // Determine the family root: use ParentTokenID if set, otherwise the token itself is the root | ||
| familyID := reusedToken.ParentTokenID | ||
| if familyID == "" { | ||
| familyID = reusedToken.ID | ||
| } | ||
|
|
||
| revokedCount, err := s.store.RevokeTokenFamily(familyID) |
| } | ||
|
|
||
| // Record metrics for each revoked token | ||
| if revokedCount > 0 { |
| require.NoError(t, err) | ||
| assert.Equal(t, models.TokenStatusRevoked, newToken.Status) | ||
| } | ||
|
|
- Add TokenFamilyID field to AccessToken model for stable family tracking - Set TokenFamilyID at initial token creation (ExchangeDeviceCode, ExchangeAuthorizationCode) - Propagate TokenFamilyID through rotation chain in RefreshAccessToken - Update RevokeTokenFamily to query by token_family_id instead of parent_token_id - Fix metrics: call RecordTokenRevoked per revoked token instead of once with wrong type - Add multi-rotation replay detection test (3 rotations then replay oldest token) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
RevokeTokenFamilystore method to bulk-revoke all tokens sharing aParentTokenID(token family)CRITICALaudit event (SUSPICIOUS_ACTIVITY) with synchronous write for security monitoringTest plan
RevokeTokenFamilycorrectly revokes active family tokens, skips already-revoked, leaves unrelated tokens untouchedmake testpasses (all existing + new tests)make lintpasses🤖 Generated with Claude Code