Skip to content

Commit 9535ce7

Browse files
committed
fix(auth): Support fallback OAuth token and prefixless credential lookups in session state
Session state might store authentication responses as raw string tokens instead of AuthCredential objects, or under custom credential keys without the standard "temp:" prefix. Add robust fallback handling to resolve raw token strings, check for prefixless keys, and scan state values for any Google OAuth access tokens starting with "ya29."
1 parent 81add39 commit 9535ce7

2 files changed

Lines changed: 87 additions & 2 deletions

File tree

src/google/adk/auth/auth_handler.py

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,43 @@ def _validate(self) -> None:
7373
if not self.auth_scheme:
7474
raise ValueError("auth_scheme is empty.")
7575

76-
def get_auth_response(self, state: State) -> AuthCredential:
76+
def get_auth_response(self, state: State) -> AuthCredential | None:
77+
# 1. Try reading the temp credential key (standard ADK flow)
7778
credential_key = "temp:" + self.auth_config.credential_key
78-
return state.get(credential_key, None)
79+
val = state.get(credential_key, None)
80+
if val is not None:
81+
if isinstance(val, AuthCredential):
82+
return val
83+
if isinstance(val, str) and val:
84+
return self._build_oauth2_credential(val)
85+
86+
# 2. Try reading the credential key without the 'temp:' prefix
87+
val = state.get(self.auth_config.credential_key, None)
88+
if val is not None:
89+
if isinstance(val, AuthCredential):
90+
return val
91+
if isinstance(val, str) and val:
92+
return self._build_oauth2_credential(val)
93+
94+
# 3. Fallback: scan the state for any active Google OAuth access token (ya29.*)
95+
try:
96+
state_dict = state.to_dict() if hasattr(state, "to_dict") else state
97+
for k, v in state_dict.items():
98+
if isinstance(v, str) and v.startswith("ya29."):
99+
return self._build_oauth2_credential(v)
100+
except Exception: # pylint: disable=broad-except
101+
pass
102+
103+
return None
104+
105+
def _build_oauth2_credential(self, token: str) -> AuthCredential:
106+
from .auth_credential import AuthCredentialTypes
107+
from .auth_credential import OAuth2Auth
108+
109+
return AuthCredential(
110+
auth_type=AuthCredentialTypes.OAUTH2,
111+
oauth2=OAuth2Auth(access_token=token),
112+
)
79113

80114
def generate_auth_request(self) -> AuthConfig:
81115
if not isinstance(

tests/unittests/auth/test_auth_handler.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,57 @@ def test_get_auth_response_not_exists(self, auth_config):
503503
result = handler.get_auth_response(state)
504504
assert result is None
505505

506+
def test_get_auth_response_temp_prefix_str_token(self, auth_config):
507+
"""Test retrieving a string token stored under temp prefix in state."""
508+
handler = AuthHandler(auth_config)
509+
state = MockState()
510+
credential_key = auth_config.credential_key
511+
state["temp:" + credential_key] = "ya29.mock_token"
512+
513+
result = handler.get_auth_response(state)
514+
515+
assert result is not None
516+
assert result.auth_type == AuthCredentialTypes.OAUTH2
517+
assert result.oauth2.access_token == "ya29.mock_token"
518+
519+
def test_get_auth_response_no_prefix_credential(
520+
self, auth_config, oauth2_credentials_with_auth_uri
521+
):
522+
"""Test retrieving a credential stored under the key without prefix."""
523+
handler = AuthHandler(auth_config)
524+
state = MockState()
525+
credential_key = auth_config.credential_key
526+
state[credential_key] = oauth2_credentials_with_auth_uri
527+
528+
result = handler.get_auth_response(state)
529+
530+
assert result == oauth2_credentials_with_auth_uri
531+
532+
def test_get_auth_response_no_prefix_str_token(self, auth_config):
533+
"""Test retrieving a string token stored under the key without prefix."""
534+
handler = AuthHandler(auth_config)
535+
state = MockState()
536+
credential_key = auth_config.credential_key
537+
state[credential_key] = "ya29.mock_token_no_prefix"
538+
539+
result = handler.get_auth_response(state)
540+
541+
assert result is not None
542+
assert result.auth_type == AuthCredentialTypes.OAUTH2
543+
assert result.oauth2.access_token == "ya29.mock_token_no_prefix"
544+
545+
def test_get_auth_response_fallback_google_token(self, auth_config):
546+
"""Test retrieving fallback Google token from state via scanning."""
547+
handler = AuthHandler(auth_config)
548+
state = MockState()
549+
state["some_other_key"] = "ya29.fallback_google_token"
550+
551+
result = handler.get_auth_response(state)
552+
553+
assert result is not None
554+
assert result.auth_type == AuthCredentialTypes.OAUTH2
555+
assert result.oauth2.access_token == "ya29.fallback_google_token"
556+
506557

507558
class TestParseAndStoreAuthResponse:
508559
"""Tests for the parse_and_store_auth_response method."""

0 commit comments

Comments
 (0)