Skip to content

fix(auth): persist refreshed OAuth2 credentials to store#5350

Open
voidborne-d wants to merge 1 commit intogoogle:mainfrom
voidborne-d:fix/persist-refreshed-oauth2-credential
Open

fix(auth): persist refreshed OAuth2 credentials to store#5350
voidborne-d wants to merge 1 commit intogoogle:mainfrom
voidborne-d:fix/persist-refreshed-oauth2-credential

Conversation

@voidborne-d
Copy link
Copy Markdown

Summary

After OAuth2CredentialRefresher.refresh() rotates the tokens in memory, the updated credential was never written back to the credential store. On the next tool invocation, get_credential() deserializes the stale pre-refresh dict, getting expired tokens.

For providers that rotate refresh_token on each refresh (Salesforce, many OIDC providers), this causes the subsequent refresh attempt to fail — the old refresh_token has already been invalidated — forcing a full re-authorization flow.

Root cause

OAuth2CredentialRefresher.refresh() mutates the AuthCredential object in memory via update_credential_with_tokens(). However, ToolContextCredentialStore.get_credential() deserializes a fresh Pydantic model from the stored dict on each call — the in-memory mutation is disconnected from the persisted state.

The class already has a _store_credential() method that serializes and writes to tool_context.state. It is called after fetching new credentials from an auth response (line 332), but was missing after the refresh path.

Fix

Call self._store_credential(existing_credential) immediately after a successful refresh in _get_existing_credential() (1 line).

Test

Added test_refreshed_credential_is_persisted_to_store — verifies that after a token refresh, the credential store contains the new access_token and refresh_token, not the stale ones.

Fixes #5329

After `OAuth2CredentialRefresher.refresh()` rotates the tokens in memory,
the updated credential was never written back to the credential store.
On the next tool invocation, `get_credential()` deserialized the stale
pre-refresh dict and returned expired tokens.  For providers that rotate
refresh_tokens on each refresh (Salesforce, many OIDC providers), this
caused the subsequent refresh attempt to fail — the old refresh_token was
already invalidated — forcing a full re-authorization flow.

The fix calls `_store_credential()` immediately after a successful
refresh so that the new access_token and refresh_token are persisted.

Fixes google#5329
@adk-bot adk-bot added the core [Component] This issue is related to the core interface and implementation label Apr 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core [Component] This issue is related to the core interface and implementation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ToolAuthHandler._get_existing_credential refreshes OAuth2 credentials in memory but doesn't persist them

2 participants