Repository navigation
persist rotated refresh tokens - #378
Open
fredrikekre wants to merge 2 commits into
Open
fredrikekre wants to merge 2 commits into
fredrikekre wants to merge 2 commits into
Conversation
rbw only deserializes access_token from the refresh response, so a rotated refresh_token is dropped and the original is replayed on every subsequent refresh. Harmless against Bitwarden, which does not rotate, but breaks every refresh after login against an SSO-backed Vaultwarden, where the identity provider consumes the token on exchange. Deserialize it, thread it through with_exchange_refresh_token, and persist it at the call sites. Option, so non-rotating servers keep their current behaviour. Assisted-by: Claude Code (Fable 5.1)
pschmitt
added a commit
to pschmitt/rbw
that referenced
this pull request
Sep 14, 2026
rbw only deserialized access_token from the OAuth refresh response, so a
rotated refresh_token was silently dropped and the stale one kept getting
replayed on every subsequent refresh. Harmless against Bitwarden, which
doesn't rotate, but breaks every refresh after the first one against an
SSO-backed Vaultwarden, where the identity provider consumes the refresh
token on exchange.
Deserialize it (Option, so non-rotating servers are unaffected), thread it
through as a new RefreshedTokens { access_token, refresh_token } struct
alongside the existing access_token, and persist it at every call site that
already persists a rotated access_token -- this fork has grown a much larger
surface of token-refreshing actions (org/collection management, bulk import,
mirror, TUI) than upstream, so this adapts and extends doy#378 rather
than cherry-picking it directly.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VjzznAfr5RkgQAFYKTaV9A
A refresh that fails with invalid_grant will never succeed: no password or second factor is involved, so the only thing the server can be rejecting is the refresh token itself. rbw treated it like any other error, so the agent retried on every sync_interval and kept doing so until the user happened to run rbw login. Against an SSO-backed Vaultwarden that meant one rejected exchange per interval, indefinitely. Check the response status before deserializing, classify invalid_grant as a distinct error, and clear the stored tokens when it occurs. That makes db.needs_login() true, so the next command takes the ordinary login path and the syncs in between fail locally without reaching the network. The match is on the error field rather than the status code, since RFC 6749 mandates 400 but providers also return 401; transport errors and unrecognised responses still retry as before. The tokens are only cleared if the rejected one is still the one stored. Rotating providers also return invalid_grant when two refreshes race, and there the rejected token has already been replaced by a valid one. Assisted-by: Claude Code (Fable 5.1)
Author
|
Added a second commit that stops retrying if the refresh token is rejected (partly overlaps with #362 although in that PR the agent keeps retrying). |
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.
rbw only deserializes access_token from the refresh response, so a rotated
refresh_token is dropped and the original is replayed on every subsequent
refresh. Harmless against Bitwarden, which does not rotate, but breaks every
refresh after login against an SSO-backed Vaultwarden, where the identity
provider consumes the token on exchange.
Deserialize it, thread it through with_exchange_refresh_token, and persist it
at the call sites. Option, so non-rotating servers keep their current behaviour.
I found this after being rate limited on my Authelia instance. 🤖 wrote the patch
but I have reviewed it and looks ok (although I am not a Rust programmer).