fix: Stop auth refresh race from logging out - #174
Merged
Conversation
Concurrent OIDC refresh calls could reuse a rotated refresh token. Zitadel returned RefreshTokenInvalid and the client cleared auth, which stopped heartbeats and dropped published tunnels. Serialize refreshes behind a mutex, keep the session on rotation races (the same rule cloud-portal uses), and add unit tests for the paths that caused daily logouts in #48.
mattdjenkinson
marked this pull request as ready for review
August 21, 2026 09:11
mattdjenkinson
requested review from
drewr,
kevwilliams,
kprabhak and
vvoytovych
August 21, 2026 09:11
kevwilliams
approved these changes
Aug 21, 2026
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
Users reported tunnels going dead overnight while Datum Desktop still looked logged in, or flipping to logged-out state after a refresh failure. The underlying issue was concurrent OIDC refresh calls reusing the same refresh token after Zitadel had already rotated it. The loser got
RefreshTokenInvalid, the client treated that as a real logout, heartbeats stopped, and every hostname on the connector started returning 503.This change serializes refresh behind a mutex so only one exchange runs at a time, reloads tokens after acquiring the lock, and skips redundant refreshes when a waiter already picked up a fresh token. When Zitadel rejects a refresh token as invalid or already rotated, we keep the current session instead of clearing auth, matching how cloud-portal handles the same error. Genuine failures (network errors, missing refresh token with no valid access token path) still log the user out. Successful refreshes now log at
infowithexpires_atso defaultui.logshows granted refreshes, not just warnings.Related analysis: datum-cloud/app#48
Test plan
cargo test -p lib(46 tests, including 12 refresh-race tests)force_refreshandload_refreshedpaths covered by unit testsui.logshowsAccess token refreshedon timer refreshRelated to #48