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

@adk-bot adk-bot added the core [Component] This issue is related to the core interface and implementation label Apr 15, 2026
@rohityan rohityan self-assigned this Apr 16, 2026
@voidborne-d voidborne-d force-pushed the fix/persist-refreshed-oauth2-credential branch 4 times, most recently from 7a378aa to a54de92 Compare April 20, 2026 12:07
@rohityan rohityan added the needs review [Status] The PR/issue is awaiting review from the maintainer label Apr 20, 2026
@rohityan
Copy link
Copy Markdown
Collaborator

Hi @voidborne-d , Thank you for your contribution! We appreciate you taking the time to submit this pull request. Your PR has been received by the team and is currently under review. We will provide feedback as soon as we have an update to share.

@rohityan
Copy link
Copy Markdown
Collaborator

Hi @DeanChensj , can you please review this.

@voidborne-d voidborne-d force-pushed the fix/persist-refreshed-oauth2-credential branch 3 times, most recently from 0cac6ba to 5b303e7 Compare April 21, 2026 13:06
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
@voidborne-d voidborne-d force-pushed the fix/persist-refreshed-oauth2-credential branch from 5b303e7 to 0ef3d8c Compare April 21, 2026 18:07
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 needs review [Status] The PR/issue is awaiting review from the maintainer

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

3 participants