From d50606229502a1cc5c3557cc9ae9f62f69c360a4 Mon Sep 17 00:00:00 2001 From: jayy-77 <1427jay@gmail.com> Date: Sat, 14 Feb 2026 20:07:21 +0530 Subject: [PATCH] Introduce normalize_oauth2_tokens function for token validation and normalization --- .../exchanger/oauth2_credential_exchanger.py | 33 ++++++++++++++----- src/google/adk/auth/oauth2_credential_util.py | 13 ++++++++ .../refresher/oauth2_credential_refresher.py | 2 ++ 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/google/adk/auth/exchanger/oauth2_credential_exchanger.py b/src/google/adk/auth/exchanger/oauth2_credential_exchanger.py index 02365f3026..5d157178ff 100644 --- a/src/google/adk/auth/exchanger/oauth2_credential_exchanger.py +++ b/src/google/adk/auth/exchanger/oauth2_credential_exchanger.py @@ -25,6 +25,7 @@ from google.adk.auth.auth_schemes import OAuthGrantType from google.adk.auth.auth_schemes import OpenIdConnectWithConfig from google.adk.auth.oauth2_credential_util import create_oauth2_session +from google.adk.auth.oauth2_credential_util import normalize_oauth2_tokens from google.adk.auth.oauth2_credential_util import update_credential_with_tokens from google.adk.utils.feature_decorator import experimental from typing_extensions import override @@ -193,19 +194,35 @@ async def _exchange_authorization_code( return ExchangeResult(auth_credential, False) try: - tokens = client.fetch_token( - token_endpoint, - authorization_response=self._normalize_auth_uri( + token_auth_method = ( + auth_credential.oauth2.token_endpoint_auth_method + if auth_credential.oauth2 + else None + ) + fetch_token_kwargs = { + 'authorization_response': self._normalize_auth_uri( auth_credential.oauth2.auth_response_uri ), - code=auth_credential.oauth2.auth_code, - grant_type=OAuthGrantType.AUTHORIZATION_CODE, - client_id=auth_credential.oauth2.client_id, - ) + 'code': auth_credential.oauth2.auth_code, + 'grant_type': OAuthGrantType.AUTHORIZATION_CODE, + } + # For client_secret_post, Authlib already includes client_id in POST body + # from OAuth2Session. Passing client_id again can duplicate parameters. + if token_auth_method != 'client_secret_post': + fetch_token_kwargs['client_id'] = auth_credential.oauth2.client_id + + tokens = client.fetch_token(token_endpoint, **fetch_token_kwargs) + tokens = normalize_oauth2_tokens(tokens) update_credential_with_tokens(auth_credential, tokens) logger.debug("Successfully exchanged authorization code for access token") except Exception as e: - logger.error("Failed to exchange authorization code: %s", e) + logger.error( + "Failed to exchange authorization code (token_endpoint_auth_method=%s): %s", + auth_credential.oauth2.token_endpoint_auth_method + if auth_credential.oauth2 + else None, + e, + ) return ExchangeResult(auth_credential, False) return ExchangeResult(auth_credential, True) diff --git a/src/google/adk/auth/oauth2_credential_util.py b/src/google/adk/auth/oauth2_credential_util.py index d2f40b339f..24225c9c8e 100644 --- a/src/google/adk/auth/oauth2_credential_util.py +++ b/src/google/adk/auth/oauth2_credential_util.py @@ -14,6 +14,7 @@ from __future__ import annotations +from collections.abc import Mapping import logging from typing import Optional from typing import Tuple @@ -97,6 +98,17 @@ def create_oauth2_session( ) +@experimental +def normalize_oauth2_tokens(tokens: object) -> OAuth2Token: + """Validates and normalizes token payload returned by OAuth libraries.""" + if not isinstance(tokens, Mapping): + raise ValueError( + "OAuth2 token response must be a dict-like object, got " + f"{type(tokens).__name__}." + ) + return tokens # type: ignore[return-value] + + @experimental def update_credential_with_tokens( auth_credential: AuthCredential, tokens: OAuth2Token @@ -107,6 +119,7 @@ def update_credential_with_tokens( auth_credential: The authentication credential to update. tokens: The OAuth2Token object containing new token information. """ + tokens = normalize_oauth2_tokens(tokens) auth_credential.oauth2.access_token = tokens.get("access_token") auth_credential.oauth2.refresh_token = tokens.get("refresh_token") auth_credential.oauth2.expires_at = ( diff --git a/src/google/adk/auth/refresher/oauth2_credential_refresher.py b/src/google/adk/auth/refresher/oauth2_credential_refresher.py index 1c600db24c..e651db3b54 100644 --- a/src/google/adk/auth/refresher/oauth2_credential_refresher.py +++ b/src/google/adk/auth/refresher/oauth2_credential_refresher.py @@ -23,6 +23,7 @@ from google.adk.auth.auth_credential import AuthCredential from google.adk.auth.auth_schemes import AuthScheme from google.adk.auth.oauth2_credential_util import create_oauth2_session +from google.adk.auth.oauth2_credential_util import normalize_oauth2_tokens from google.adk.auth.oauth2_credential_util import update_credential_with_tokens from google.adk.utils.feature_decorator import experimental from google.auth.transport.requests import Request @@ -115,6 +116,7 @@ async def refresh( url=token_endpoint, refresh_token=auth_credential.oauth2.refresh_token, ) + tokens = normalize_oauth2_tokens(tokens) update_credential_with_tokens(auth_credential, tokens) logger.debug("Successfully refreshed OAuth2 tokens") except Exception as e: