fix: preserve the existing refresh token when a refresh response omits one - #64
Open
nnabeyang wants to merge 3 commits into
Open
fix: preserve the existing refresh token when a refresh response omits one#64nnabeyang wants to merge 3 commits into
nnabeyang wants to merge 3 commits into
Conversation
🦋 Changeset detectedLatest commit: 4d5f120 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
nnabeyang
force-pushed
the
fix/preserve-refresh-token-when-omitted
branch
from
August 21, 2026 06:03
81976ed to
4d5f120
Compare
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.
Not every authorization server rotates refresh tokens. RFC 6749 §6 leaves that to the server:
RFC 9700 §2.2.2 spells out when rotation is not required at all:
A DPoP-bound refresh token satisfies the first branch, so a server issuing one is under no obligation to rotate.
A response that carries no
refresh_tokentherefore leaves the client's existing one in force. OAuth4Swift instead discarded it: after such a refresh the session held no refresh token at all, could never refresh again, and signed the user out as soon as the access token expired. Against a server that does not rotate, that happened on the very first refresh.The refresh path now keeps the refresh token it presented. A response that does carry a
refresh_tokenstill replaces the stored one.Two details come with keeping it:
The preserved token's
fetchedOnmoves to now. It records when the token was last exchanged, andrefresh(debounce:)reads it to decide whether a refresh is due, so leaving it behind would keep the debounce permanently satisfied against a non-rotating server.A
refresh_token_timeoutthat arrives without arefresh_tokenis applied to the preserved token, per draft-ietf-oauth-refresh-token-expiration, whichTokenEndpointResponsealready cites for that field:The field is a sliding window — "the time in seconds that the refresh token may be held by the client without exchanging" — so ignoring it would expire a token the server still honors. Without the field the existing expiry is left alone.
The first commit adds three regression tests: the token survives a response that omits it, the session can refresh again afterwards, and a
refresh_token_timeoutwithout arefresh_tokenrestates the preserved token's expiry. All three fail against the previous implementation. The second commit applies the fix and makes the complete suite pass.