Skip to content

fix(auth): omit scope from OAuth2 refresh requests and guard None scopes#5352

Open
voidborne-d wants to merge 1 commit intogoogle:mainfrom
voidborne-d:fix/oauth2-refresh-scope-omission
Open

fix(auth): omit scope from OAuth2 refresh requests and guard None scopes#5352
voidborne-d wants to merge 1 commit intogoogle:mainfrom
voidborne-d:fix/oauth2-refresh-scope-omission

Conversation

@voidborne-d
Copy link
Copy Markdown

Summary

Fixes #5328 — OAuth2 token refresh fails for providers that reject the scope parameter (e.g. Salesforce).

Problem

Two related issues in the OAuth2 credential flow:

  1. Scope in refresh requests: OAuth2CredentialRefresher.refresh() creates an OAuth2Session with scopes, and authlib automatically includes scope in the refresh token request body. Per RFC 6749 §6, scope is OPTIONAL in refresh requests — when omitted, providers treat it as equal to the originally-granted scope. Some providers (e.g. Salesforce) actively reject refresh requests that include scope.

  2. None scopes crash: create_oauth2_session() calls .scopes.keys() without checking for None. When OAuthFlowClientCredentials or OAuthFlowAuthorizationCode is created without specifying scopes (valid per OpenAPI spec), this crashes with AttributeError: NoneType has no attribute keys.

Fix

  1. oauth2_credential_refresher.py: Pass scope="" to client.refresh_token(). This prevents authlib from injecting the session's scope into the refresh request, since "scope" in kwargs is True. The empty string is falsy, so authlib's prepare_token_request omits it from the POST body entirely.

  2. oauth2_credential_util.py: Guard scopes against None in both authorizationCode and clientCredentials flow branches, defaulting to [].

Tests

  • test_refresh_omits_scope_from_request — verifies refresh_token() is called with scope="" to suppress scope injection.
  • test_create_oauth2_session_client_credentials_none_scopes — verifies no crash when clientCredentials flow has no scopes.
  • test_create_oauth2_session_auth_code_none_scopes — verifies no crash when authorizationCode flow has no scopes.

Related

@adk-bot adk-bot added the core [Component] This issue is related to the core interface and implementation label Apr 16, 2026
@rohityan rohityan self-assigned this Apr 16, 2026
Per RFC 6749 §6, the scope parameter is OPTIONAL in refresh requests —
when omitted, providers treat it as equal to the originally-granted
scope.  Some providers (e.g. Salesforce) actively reject refresh
requests that include a scope parameter, causing silent token refresh
failures.

Changes:
1. oauth2_credential_refresher.py: Pass scope="" to client.refresh_token()
   to prevent authlib from injecting the session's scope into the refresh
   request body.  Empty string is falsy, so authlib's prepare_token_request
   omits it entirely from the POST body.

2. oauth2_credential_util.py: Guard against None scopes in both
   authorizationCode and clientCredentials flows.  When scopes are not
   specified (valid per OpenAPI spec), default to an empty list instead
   of crashing with AttributeError: 'NoneType' has no attribute 'keys'.

Fixes google#5328
@voidborne-d voidborne-d force-pushed the fix/oauth2-refresh-scope-omission branch from df4e848 to eb65de1 Compare April 18, 2026 05:09
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.

OAuth2 token refresh fails for providers that reject scope parameter (e.g. Salesforce)

3 participants