Repository navigation
Revoke the grant when a rotated refresh token is replayed, with a 60s retry grace #3160
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
694da92
Revoke the grant when a rotated refresh token is replayed, with a ret…
jeremy 136b9bf
Read a grace retry's successor before checking that it's still current
jeremy 63c2113
Judge supersession by the successor a rotation produced, not by times…
jeremy 3587f6c
Lock a grant before deleting its retired tokens
jeremy 8a8dd9f
Weigh the scope of a grace retry as the live refresh it repeats
jeremy 5286850
Refuse a grace retry that asks for a different scope than its rotatio…
jeremy 0956b57
Refuse a superseded retry as superseded before weighing its scope
jeremy 3ec6822
Merge oauth-bearer-challenge: its migrations can be run again after s…
jeremy e149cb2
Merge oauth-bearer-challenge
jeremy 6686534
Merge oauth-bearer-challenge into oauth-refresh-replay
jeremy 49101d9
Merge oauth-bearer-challenge into oauth-refresh-replay
jeremy d5cf7ec
Merge oauth-bearer-challenge into oauth-refresh-replay
jeremy 65531ca
Merge oauth-bearer-challenge into oauth-refresh-replay
jeremy 8fb89c9
Redact retired refresh tokens from logged SQL
jeremy 4e397ad
Merge oauth-bearer-challenge into oauth-refresh-replay
jeremy 07682a2
Read a replayed refresh token's grant past the query cache
jeremy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| # A refresh token its grant has rotated away from. Presenting one again is | ||
| # either a client retrying a rotation whose response it lost, or someone | ||
| # replaying a stolen token (OAuth 2.1 §4.3.1). Within GRACE of the rotation, | ||
| # and only while the successor it produced is still the grant's current | ||
| # refresh token, it's a retry. | ||
| # Otherwise it's a replay, and the whole grant is revoked. | ||
| class Oauth::RetiredRefreshToken < ApplicationRecord | ||
| # bc3's refresh_replay_grace default (Oauth::RefreshToken::Rotation). | ||
| GRACE = 60.seconds | ||
|
|
||
| # As long as a replay can still be recognized. It matches bc3's 90-day | ||
| # refresh_token_ttl, after which a rotated token there has expired too. | ||
| RETENTION = 90.days | ||
|
jeremy marked this conversation as resolved.
|
||
|
|
||
| belongs_to :access_token, class_name: "Identity::AccessToken" | ||
|
|
||
| scope :stale, -> { where(created_at: ...RETENTION.ago) } | ||
|
|
||
| class << self | ||
| def cleanup | ||
| stale.in_batches.delete_all | ||
| end | ||
| end | ||
|
|
||
| # Whether a replay now is a retry the grant can answer with the successor | ||
| # this rotation produced. Pass the grant as just read: comparing against | ||
| # that one read, rather than querying again, leaves no gap for a concurrent | ||
| # rotation to slip into. | ||
| def retryable?(grant) | ||
| within_grace? && !superseded?(grant) | ||
| end | ||
|
|
||
| def within_grace? | ||
| created_at > GRACE.ago | ||
|
jeremy marked this conversation as resolved.
|
||
| end | ||
|
|
||
| # The successor this rotation produced has been rotated in turn, so the | ||
| # client demonstrably received it and moved on. This is read from the | ||
| # grant's current token, never from timestamp order, which skewed host | ||
| # clocks could invert. | ||
| def superseded?(grant) | ||
| grant.refresh_token != successor_refresh_token | ||
| end | ||
| end | ||
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
14 changes: 14 additions & 0 deletions
14
db/migrate/20261006120000_create_oauth_retired_refresh_tokens.rb
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| class CreateOauthRetiredRefreshTokens < ActiveRecord::Migration[8.2] | ||
| def change | ||
| create_table :oauth_retired_refresh_tokens, id: :uuid do |t| | ||
| t.uuid :access_token_id, null: false | ||
| t.string :refresh_token, null: false | ||
| t.string :successor_refresh_token, null: false | ||
|
jeremy marked this conversation as resolved.
|
||
| t.datetime :created_at, null: false | ||
|
|
||
| t.index :refresh_token, unique: true | ||
| t.index :access_token_id | ||
| t.index :created_at | ||
| end | ||
| end | ||
| end | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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
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
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
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.