Skip to content

Disable async token refresh for GCP credential providers#1575

Merged
renaudhartert-db merged 3 commits intomainfrom
disable-gcp-async-refresh
Mar 22, 2026
Merged

Disable async token refresh for GCP credential providers#1575
renaudhartert-db merged 3 commits intomainfrom
disable-gcp-async-refresh

Conversation

@renaudhartert-db
Copy link
Copy Markdown
Contributor

@renaudhartert-db renaudhartert-db commented Mar 21, 2026

Changes

Fixes #1549. Related: #1550, #1573.

Google's Cloud Go libraries (impersonate.IDTokenSource, impersonate.CredentialsTokenSource, idtoken.NewTokenSource, google.CredentialsFromJSON) all internally wrap their token sources in oauth2.ReuseTokenSource. This means the SDK's cachedTokenSource async refresh is swallowed by the inner ReuseTokenSource cache, making it entirely wasted work.

This is the same double-caching bug as M2M OAuth (#1549). Though, unlike M2M (#1550) and Azure Client Secret (#1573), Google's libraries don't expose an uncached Token(ctx) method -- the inner sources are unexported types. Recreating the token source on each call is expensive and will require further considerations.

The pragmatic fix is to disable async refresh for both GCP providers:

  • GoogleDefaultCredentials (config/auth_gcp_google_id.go)
  • GoogleCredentials (config/auth_gcp_google_credentials.go)

Google's own ReuseTokenSource (with a 10-second early refresh window) still provides token renewal before expiry. Thorough comments explain the rationale and warn against re-enabling async refresh.

Tests

Existing config tests pass (go test ./config/ -v -count=1). No new tests needed -- this is a configuration change (appending auth.WithAsyncRefresh(false) to cache options) and the async refresh mechanism itself is already well-tested.

Google's Cloud Go libraries internally wrap token sources in
oauth2.ReuseTokenSource, causing the SDK's async refresh (at T-20min)
to be swallowed by the inner cache. Unlike M2M OAuth and Azure Client
Secret, the inner sources are unexported so we cannot bypass the
caching. Disable async refresh for both GoogleDefaultCredentials and
GoogleCredentials providers to avoid wasted refresh attempts.

Fixes #1549

Co-authored-by: Isaac
Signed-off-by: Ubuntu <renaud.hartert@databricks.com>
Co-authored-by: Isaac
Signed-off-by: Ubuntu <renaud.hartert@databricks.com>
Copy link
Copy Markdown
Member

@simonfaltum simonfaltum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Swarm: Disable async token refresh for GCP credential providers

Verdict: Approved | Reviewers: Isaac + Cursor | Rounds: 3 (with debate)

0 Critical | 0 Major | 0 Gap | 1 Nit | 2 Suggestion

Debate Summary

The central disagreement across 3 rounds concerned whether disabling async refresh while keeping the SDK's outer cache creates a correctness issue:

  • Cursor's position (maintained through Round 3): With async disabled, the SDK's outer cachedTokenSource uses blockingToken() which only calls through to Google's source after exact expiry. Since Google's ReuseTokenSource is lazy (not background-refreshing), its 10-second early-refresh window is effectively bypassed. This means the PR doesn't fully fix #1549 if interpreted as fixing near-expiry auth failures.

  • Isaac's position (maintained through Round 3): The PR's goal is to eliminate wasted async refresh work, and it achieves that. The async path was already broken for GCP (never reliably caught Google's 10-second window). GCP doesn't reject near-expiry tokens the way Azure does. The alternative (removing the outer cache) is a larger change with marginal practical benefit.

  • Synthesis: Both reviewers are technically correct about different aspects. The PR correctly fixes the stated problem (wasted async refresh). Whether #1549 also implies fixing the near-expiry window is a scope question for the PR author. The practical risk is low since GCP doesn't reject near-expiry tokens.

See inline comments for specific findings.


Automated deep review by Isaac + Cursor swarm (3 rounds)

@github-actions
Copy link
Copy Markdown

If integration tests don't run automatically, an authorized user can run them manually by following the instructions below:

Trigger:
go/deco-tests-run/sdk-go

Inputs:

  • PR number: 1575
  • Commit SHA: d0c5a4bf54b2be02bdbbfdc72dde1296a03c21ac

Checks will be approved automatically on success.

@renaudhartert-db renaudhartert-db added this pull request to the merge queue Mar 22, 2026
Merged via the queue into main with commit 93848c4 Mar 22, 2026
13 checks passed
@renaudhartert-db renaudhartert-db deleted the disable-gcp-async-refresh branch March 22, 2026 08:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[ISSUE] M2M OAuth: double-caching causes async token refresh to be ineffective until ~10s before expiry, causing 401 bursts

2 participants