Skip to content

Commit 6ce4b87

Browse files
doughaydenboyangsvl
authored andcommitted
fix(auth): omit scope from OAuth2 token requests
- Stop setting scope on the shared create_oauth2_session helper - Drops scope from both token exchange and refresh requests - Neither needs scope (RFC 6749 4.1.3, 6); some providers reject it - Auth URL construction in auth_handler.py keeps scope, unchanged - Add body-level tests asserting refresh and exchange omit scope Merge #5874 Change-Id: I78e7e1e69076afcc13460b7965ca05cae734814e
1 parent 9126acb commit 6ce4b87

2 files changed

Lines changed: 78 additions & 4 deletions

File tree

src/google/adk/auth/oauth2_credential_util.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,21 +49,18 @@ def create_oauth2_session(
4949
logger.warning("OpenIdConnect scheme missing token_endpoint")
5050
return None, None
5151
token_endpoint = auth_scheme.token_endpoint
52-
scopes = auth_scheme.scopes or []
5352
elif isinstance(auth_scheme, OAuth2):
5453
# Support both authorization code and client credentials flows
5554
if (
5655
auth_scheme.flows.authorizationCode
5756
and auth_scheme.flows.authorizationCode.tokenUrl
5857
):
5958
token_endpoint = auth_scheme.flows.authorizationCode.tokenUrl
60-
scopes = list(auth_scheme.flows.authorizationCode.scopes.keys())
6159
elif (
6260
auth_scheme.flows.clientCredentials
6361
and auth_scheme.flows.clientCredentials.tokenUrl
6462
):
6563
token_endpoint = auth_scheme.flows.clientCredentials.tokenUrl
66-
scopes = list(auth_scheme.flows.clientCredentials.scopes.keys())
6764
else:
6865
logger.warning(
6966
"OAuth2 scheme missing required flow configuration. Expected either"
@@ -84,11 +81,12 @@ def create_oauth2_session(
8481
):
8582
return None, None
8683

84+
# Scope is intentionally omitted: token exchange and refresh don't require
85+
# it per RFC 6749, and some providers reject it on these requests.
8786
return (
8887
OAuth2Session(
8988
auth_credential.oauth2.client_id,
9089
auth_credential.oauth2.client_secret,
91-
scope=" ".join(scopes),
9290
redirect_uri=auth_credential.oauth2.redirect_uri,
9391
state=auth_credential.oauth2.state,
9492
token_endpoint_auth_method=auth_credential.oauth2.token_endpoint_auth_method,

tests/unittests/auth/test_oauth2_credential_util.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,82 @@ def test_create_oauth2_session_oauth2_scheme_with_token_endpoint_auth_method(
207207
assert token_endpoint == "https://example.com/token"
208208
assert client.token_endpoint_auth_method == "client_secret_jwt"
209209

210+
def _oauth2_scheme_with_scopes(self):
211+
"""Build an OAuth2 scheme that declares scopes."""
212+
return OAuth2(
213+
type_="oauth2",
214+
flows=OAuthFlows(
215+
authorizationCode=OAuthFlowAuthorizationCode(
216+
authorizationUrl="https://example.com/auth",
217+
tokenUrl="https://example.com/token",
218+
scopes={"read": "Read access", "write": "Write access"},
219+
)
220+
),
221+
)
222+
223+
def _capturing_post(self, captured):
224+
"""Stub for OAuth2Session.post that records the token-request body."""
225+
226+
def _post(*args, **kwargs):
227+
captured["data"] = kwargs.get("data")
228+
response = Mock()
229+
response.status_code = 200
230+
response.json.return_value = {
231+
"access_token": "new_access_token",
232+
"token_type": "Bearer",
233+
"expires_in": 3600,
234+
"refresh_token": "new_refresh_token",
235+
}
236+
return response
237+
238+
return _post
239+
240+
def test_refresh_request_omits_scope(self):
241+
"""Refresh requests must not carry scope (some providers reject it)."""
242+
credential = AuthCredential(
243+
auth_type=AuthCredentialTypes.OAUTH2,
244+
oauth2=OAuth2Auth(
245+
client_id="test_client_id",
246+
client_secret="test_client_secret",
247+
redirect_uri="https://example.com/callback",
248+
),
249+
)
250+
251+
client, token_endpoint = create_oauth2_session(
252+
self._oauth2_scheme_with_scopes(), credential
253+
)
254+
assert client is not None
255+
256+
captured = {}
257+
client.post = self._capturing_post(captured)
258+
client.refresh_token(token_endpoint, refresh_token="old_refresh_token")
259+
260+
assert "scope" not in captured["data"]
261+
262+
def test_token_exchange_omits_scope(self):
263+
"""Authorization-code exchange must not carry scope (it is redundant)."""
264+
credential = AuthCredential(
265+
auth_type=AuthCredentialTypes.OAUTH2,
266+
oauth2=OAuth2Auth(
267+
client_id="test_client_id",
268+
client_secret="test_client_secret",
269+
redirect_uri="https://example.com/callback",
270+
),
271+
)
272+
273+
client, token_endpoint = create_oauth2_session(
274+
self._oauth2_scheme_with_scopes(), credential
275+
)
276+
assert client is not None
277+
278+
captured = {}
279+
client.post = self._capturing_post(captured)
280+
client.fetch_token(
281+
token_endpoint, grant_type="authorization_code", code="test_code"
282+
)
283+
284+
assert "scope" not in captured["data"]
285+
210286
def test_update_credential_with_tokens(self):
211287
"""Test update_credential_with_tokens function."""
212288
credential = AuthCredential(

0 commit comments

Comments
 (0)