Skip to content

Commit 2b46204

Browse files
caohy1988claude
andcommitted
fix(auth): migrate credential storage to secret: scope (Phase 2)
Migrate existing credential writers to use the `secret:` prefix so that OAuth tokens and credentials are never persisted to session storage backends. - Change BIGQUERY_TOKEN_CACHE_KEY to "secret:bigquery_token_cache" - Update SessionStateCredentialService.save_credential and load_credential to prefix credential_key with State.SECRET_PREFIX - Update tests to expect secret-prefixed state keys This is a breaking change for existing sessions: cached credentials under the old unprefixed keys will not be found, requiring re-authentication. This is intentional — the old behavior stored credentials in plaintext in session backends. Depends on #5132 (Phase 1: secret: scope infrastructure) Closes #5112 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ce8d2a3 commit 2b46204

File tree

3 files changed

+23
-17
lines changed

3 files changed

+23
-17
lines changed

src/google/adk/auth/credential_service/session_state_credential_service.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from typing_extensions import override
2020

2121
from ...agents.callback_context import CallbackContext
22+
from ...sessions.state import State
2223
from ...utils.feature_decorator import experimental
2324
from ..auth_credential import AuthCredential
2425
from ..auth_tool import AuthConfig
@@ -54,7 +55,9 @@ async def load_credential(
5455
Optional[AuthCredential]: the credential saved in the store.
5556
5657
"""
57-
return callback_context.state.get(auth_config.credential_key)
58+
return callback_context.state.get(
59+
State.SECRET_PREFIX + auth_config.credential_key
60+
)
5861

5962
@override
6063
async def save_credential(
@@ -78,6 +81,6 @@ async def save_credential(
7881
None
7982
"""
8083

81-
callback_context.state[auth_config.credential_key] = (
84+
callback_context.state[State.SECRET_PREFIX + auth_config.credential_key] = (
8285
auth_config.exchanged_auth_credential
8386
)

src/google/adk/tools/bigquery/bigquery_credentials.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from ...features import FeatureName
1919
from .._google_credentials import BaseGoogleCredentialsConfig
2020

21-
BIGQUERY_TOKEN_CACHE_KEY = "bigquery_token_cache"
21+
BIGQUERY_TOKEN_CACHE_KEY = "secret:bigquery_token_cache"
2222
BIGQUERY_SCOPES = [
2323
"https://www.googleapis.com/auth/bigquery",
2424
"https://www.googleapis.com/auth/dataplex.read-write",

tests/unittests/auth/credential_service/test_session_state_credential_service.py

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from google.adk.auth.auth_credential import OAuth2Auth
2424
from google.adk.auth.auth_tool import AuthConfig
2525
from google.adk.auth.credential_service.session_state_credential_service import SessionStateCredentialService
26+
from google.adk.sessions.state import State
2627
import pytest
2728

2829

@@ -265,10 +266,11 @@ async def test_state_persistence_across_operations(
265266
# Save credential
266267
await credential_service.save_credential(auth_config, callback_context)
267268

268-
# Verify state contains the credential
269-
assert auth_config.credential_key in callback_context.state
269+
# Verify state contains the credential under secret: prefix
270+
secret_key = State.SECRET_PREFIX + auth_config.credential_key
271+
assert secret_key in callback_context.state
270272
assert (
271-
callback_context.state[auth_config.credential_key]
273+
callback_context.state[secret_key]
272274
== auth_config.exchanged_auth_credential
273275
)
274276

@@ -279,9 +281,9 @@ async def test_state_persistence_across_operations(
279281
assert result is not None
280282

281283
# Verify state still contains the credential
282-
assert auth_config.credential_key in callback_context.state
284+
assert secret_key in callback_context.state
283285
assert (
284-
callback_context.state[auth_config.credential_key]
286+
callback_context.state[secret_key]
285287
== auth_config.exchanged_auth_credential
286288
)
287289

@@ -300,7 +302,7 @@ async def test_state_persistence_across_operations(
300302
await credential_service.save_credential(auth_config, callback_context)
301303

302304
# Verify state was updated
303-
assert callback_context.state[auth_config.credential_key] == new_credential
305+
assert callback_context.state[secret_key] == new_credential
304306

305307
@pytest.mark.asyncio
306308
async def test_credential_key_uniqueness(
@@ -344,13 +346,12 @@ async def test_credential_key_uniqueness(
344346
await credential_service.save_credential(auth_config1, callback_context)
345347
await credential_service.save_credential(auth_config2, callback_context)
346348

347-
# Verify both exist in state with different keys
348-
assert "unique_key_1" in callback_context.state
349-
assert "unique_key_2" in callback_context.state
350-
assert (
351-
callback_context.state["unique_key_1"]
352-
!= callback_context.state["unique_key_2"]
353-
)
349+
# Verify both exist in state with secret-prefixed keys
350+
sk1 = State.SECRET_PREFIX + "unique_key_1"
351+
sk2 = State.SECRET_PREFIX + "unique_key_2"
352+
assert sk1 in callback_context.state
353+
assert sk2 in callback_context.state
354+
assert callback_context.state[sk1] != callback_context.state[sk2]
354355

355356
# Load and verify both credentials
356357
result1 = await credential_service.load_credential(
@@ -379,7 +380,9 @@ async def test_direct_state_access(
379380
redirect_uri="https://direct.com/callback",
380381
),
381382
)
382-
callback_context.state[auth_config.credential_key] = test_credential
383+
callback_context.state[State.SECRET_PREFIX + auth_config.credential_key] = (
384+
test_credential
385+
)
383386

384387
# Load using the service
385388
result = await credential_service.load_credential(

0 commit comments

Comments
 (0)