-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Description
Describe the bug
The protocol publicly share the client_secret to the ADK client. This information MUST stay confidential and never shared
To Reproduce
- Create a simple agent, with a simple tool, and use authenticated function tool
from google.adk.agents.llm_agent import Agent
from fastapi.openapi.models import OAuth2, OAuthFlows, OAuthFlowAuthorizationCode
from google.adk.auth import AuthCredential, AuthConfig, AuthCredentialTypes, OAuth2Auth
from google.adk.tools import ToolContext
from google.adk.tools.authenticated_function_tool import AuthenticatedFunctionTool
YOUR_OAUTH_CLIENT_ID = "513069150666-mkspihfonim1j4f0mfp8j2v6hkdtep6k.apps.googleusercontent.com"
YOUR_OAUTH_CLIENT_SECRET = "GOCSPX-<redacted>
def log_tool(prompt: str, tool_context: ToolContext) -> None:
"""
Log the user query with the log_tool
:param prompt: The user query to log
:param tool_context: The tool context to access credentials
:return: None
"""
print(f"DEBUG: Received prompt: {prompt}")
headers = {}
# Retrieve the credential using the context and auth config
exchanged_credential = tool_context.get_auth_response(oauth_config)
if not exchanged_credential:
exchanged_credential = tool_context.get_auth_response(oidc_config)
if exchanged_credential and exchanged_credential.oauth2:
# Log the token for debug purposes as requested
print(f"DEBUG: Found credential for user {tool_context.user_id}")
access_token = exchanged_credential.oauth2.access_token
print(f"DEBUG: Access Token: {access_token}")
else:
print("DEBUG: No credential found in tool_context")
oauth_scheme = OAuth2(
flows=OAuthFlows(
authorizationCode=OAuthFlowAuthorizationCode(
authorizationUrl="https://accounts.google.com/o/oauth2/auth",
tokenUrl="https://oauth2.googleapis.com/token",
scopes={
"https://www.googleapis.com/auth/cloud-platform": "BigQuery"
},
)
)
)
oauth_credential = AuthCredential(
auth_type=AuthCredentialTypes.OAUTH2,
oauth2=OAuth2Auth(
client_id=YOUR_OAUTH_CLIENT_ID,
client_secret=YOUR_OAUTH_CLIENT_SECRET
),
)
oauth_config = AuthConfig(
auth_scheme=oauth_scheme,
raw_auth_credential= oauth_credential
)
oauth_authenticated_log_tool = AuthenticatedFunctionTool(func=log_tool, auth_config=oauth_config, response_for_auth_required="Pending User Authorization.")
root_agent = Agent(
model='gemini-2.5-flash',
name='root_agent',
description='A helpful assistant for user questions.',
instruction='Answer user questions to the best of your knowledge. Log the user query with the log_tool',
tools=[oauth_authenticated_log_tool]
)
- Create a session
- Invoke the secure tool
Sample of ADK response to indicate to the client to run a OAuth2 flow
[
{
"modelVersion": "gemini-2.5-flash",
"content": {
"parts": [
{
"text": "Okay, let's break this down. First, I'm starting with a very simple request, in French, to get information: \"Hello, a quel projet j'ai accès?\" That's my way of asking, \"Hello, which project do I have access to?\" Pretty straightforward, really.\n\nNow, from an architectural standpoint, I know that translates directly into the capabilities of the `list_project_tool`. That tool *is* designed to tell me exactly that: which Google Cloud projects are available to me. And the beauty of this is its simplicity – `list_project_tool` doesn't need any additional parameters; it just does what it says on the tin. So, my action is clear: I need to invoke the `list_project_tool` immediately to get the project list and provide a rapid answer. This is a core, fundamental action.\n",
"thought": true
},
{
"functionCall": {
"id": "adk-278007a2-d9c0-4f42-9be3-813ade149f37",
"args": {},
"name": "list_project_tool"
},
"thoughtSignature": "CpoDAePx_16-kKCcDZLmBIV4VfmwSfm1hqaUlGKD8fEbq-gEZlt60Xr0pGKHyutmyUR3ICd_Mv5NNP2QIxfSx4qrAE9blXQG4YQesJPe0bxM3IiXZRn2FnclS7xBbRi9RfaFCGzJx1Hp_g4WQDaNZIWqaNSb7aM8Cunx7B1EXPHgNbvIJ3IeIsXEVBd24VGJmpWwjRXtbLOvFiPkRwqk4ryl27Eu3AJwzb2uIRXWGtGWdp4JpNW0nGbRgiZ7By25uVGi6ajw2TGXc8gbOmt-5Jw8yjF2ZHSNcJqcqVIT62jCw6S7ViOhrtYjGW4mw5iV4NqjLOd6HE3v4m2Kemohwqz_-6qftz7hyPiFuOFT3QAWu0SOdVC4b3UX97eBgmzikJbPuvcgYzMpMM4cXoG_19JBEii-Ih4SbfVbxoXj-v4iEU2RHvOiDocC3IXpqnTng_MsuacTLhVbd3xndeexkZNu4yqW94AH5dKKytCmM81n-DQDrEe-WuC2l-O3xTN-AUz4nlg7GlmwPN1fUAxfgTuGoByQSayiDcLZH2g="
}
],
"role": "model"
},
"finishReason": "STOP",
"usageMetadata": {
"candidatesTokenCount": 5,
"candidatesTokensDetails": [
{
"modality": "TEXT",
"tokenCount": 5
}
],
"promptTokenCount": 156,
"promptTokensDetails": [
{
"modality": "TEXT",
"tokenCount": 156
}
],
"thoughtsTokenCount": 86,
"totalTokenCount": 247,
"trafficType": "ON_DEMAND"
},
"avgLogprobs": -1.7120042800903321,
"invocationId": "e-7a667312-57eb-4955-965f-b4faf1302ce2",
"author": "space_agent",
"actions": {
"stateDelta": {},
"artifactDelta": {},
"requestedAuthConfigs": {},
"requestedToolConfirmations": {}
},
"longRunningToolIds": [],
"id": "734f13d7-d323-4a8a-bff1-da7fde72109e",
"timestamp": 1764943012.128835
},
{
"content": {
"parts": [
{
"functionCall": {
"id": "adk-4cab06d4-397d-4013-ab7b-d1ee3bafac12",
"args": {
"functionCallId": "adk-278007a2-d9c0-4f42-9be3-813ade149f37",
"authConfig": {
"authScheme": {
"type": "oauth2",
"flows": {
"authorizationCode": {
"scopes": {
"https://www.googleapis.com/auth/cloud-platform": "BigQuery"
},
"authorizationUrl": "https://accounts.google.com/o/oauth2/auth",
"tokenUrl": "https://oauth2.googleapis.com/token"
}
}
},
"rawAuthCredential": {
"authType": "oauth2",
"oauth2": {
"clientId": "513069150666-mkspihfonim1j4f0mfp8j2v6hkdtep6k.apps.googleusercontent.com",
"clientSecret": "GOCSPX-<redacted>"
}
},
"exchangedAuthCredential": {
"authType": "oauth2",
"oauth2": {
"clientId": "513069150666-mkspihfonim1j4f0mfp8j2v6hkdtep6k.apps.googleusercontent.com",
"clientSecret": "GOCSPX-<redacted>",
"authUri": "https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=513069150666-mkspihfonim1j4f0mfp8j2v6hkdtep6k.apps.googleusercontent.com&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcloud-platform&state=0eEsoJ0HwJynIGynZ0GtOZmAiJWsTz&access_type=offline&prompt=consent",
"state": "0eEsoJ0HwJynIGynZ0GtOZmAiJWsTz"
}
},
"credentialKey": "adk_oauth2_-3537330412975045649_oauth2_-8782076668427256465"
}
},
"name": "adk_request_credential"
}
}
],
"role": "user"
},
"invocationId": "e-7a667312-57eb-4955-965f-b4faf1302ce2",
"author": "space_agent",
"actions": {
"stateDelta": {},
"artifactDelta": {},
"requestedAuthConfigs": {},
"requestedToolConfirmations": {}
},
"longRunningToolIds": [
"adk-4cab06d4-397d-4013-ab7b-d1ee3bafac12"
],
"id": "65986065-222e-4ab2-b9b1-3b40e9adfa73",
"timestamp": 1764943015.619703
},
{
"content": {
"parts": [
{
"functionResponse": {
"id": "adk-278007a2-d9c0-4f42-9be3-813ade149f37",
"name": "list_project_tool",
"response": {
"result": "Pending User Authorization."
}
}
}
],
"role": "user"
},
"invocationId": "e-7a667312-57eb-4955-965f-b4faf1302ce2",
"author": "space_agent",
"actions": {
"stateDelta": {},
"artifactDelta": {},
"requestedAuthConfigs": {
"adk-278007a2-d9c0-4f42-9be3-813ade149f37": {
"authScheme": {
"type": "oauth2",
"flows": {
"authorizationCode": {
"scopes": {
"https://www.googleapis.com/auth/cloud-platform": "BigQuery"
},
"authorizationUrl": "https://accounts.google.com/o/oauth2/auth",
"tokenUrl": "https://oauth2.googleapis.com/token"
}
}
},
"rawAuthCredential": {
"authType": "oauth2",
"oauth2": {
"clientId": "513069150666-mkspihfonim1j4f0mfp8j2v6hkdtep6k.apps.googleusercontent.com",
"clientSecret": "GOCSPX-<redacted>"
}
},
"exchangedAuthCredential": {
"authType": "oauth2",
"oauth2": {
"clientId": "513069150666-mkspihfonim1j4f0mfp8j2v6hkdtep6k.apps.googleusercontent.com",
"clientSecret": "GOCSPX-<redacted>",
"authUri": "https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=513069150666-mkspihfonim1j4f0mfp8j2v6hkdtep6k.apps.googleusercontent.com&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcloud-platform&state=0eEsoJ0HwJynIGynZ0GtOZmAiJWsTz&access_type=offline&prompt=consent",
"state": "0eEsoJ0HwJynIGynZ0GtOZmAiJWsTz"
}
},
"credentialKey": "adk_oauth2_-3537330412975045649_oauth2_-8782076668427256465"
}
},
"requestedToolConfirmations": {}
},
"id": "d2beef7d-abb7-499d-8929-d4e5c11da93c",
"timestamp": 1764943015.618363
},
{
"modelVersion": "gemini-2.5-flash",
"content": {
"parts": [
{
"text": "I need your authorization to list the projects you have access to. Could you please grant it?"
}
],
"role": "model"
},
"finishReason": "STOP",
"usageMetadata": {
"candidatesTokenCount": 19,
"candidatesTokensDetails": [
{
"modality": "TEXT",
"tokenCount": 19
}
],
"promptTokenCount": 358,
"promptTokensDetails": [
{
"modality": "TEXT",
"tokenCount": 442
}
],
"totalTokenCount": 377,
"trafficType": "ON_DEMAND"
},
"avgLogprobs": -0.19399306648655942,
"invocationId": "e-7a667312-57eb-4955-965f-b4faf1302ce2",
"author": "space_agent",
"actions": {
"stateDelta": {},
"artifactDelta": {},
"requestedAuthConfigs": {},
"requestedToolConfirmations": {}
},
"id": "889fbcf7-1075-41a1-9c8c-4e46aac5acfd",
"timestamp": 1764943015.620248
}
]
Expected behavior
Not to see the client_secret, or see it redacted
Desktop (please complete the following information):
- OS: Windows with WSL Ubuntu 24
- Python version(python -V): 3.12
- ADK version(pip show google-adk): 1.19.0
Model Information:
- Which model is being used(e.g. gemini-2.5-pro): Gemini 2.5 flash
Additional context
I didn't find a way to solve this easily. There are many classes to handle the authentication (credential service, credential manager, authHandler, oauth exchanger,...)
My wish is to have a in memory map with the client_id as a key, and the client secret as value.
When the function response comes in, the auth_config must be enriched with the client_secret, based on the client secret contained in the exchanged