adk-python icon indicating copy to clipboard operation
adk-python copied to clipboard

Credential Manager mix user credential

Open guillaumeblaquiere opened this issue 1 month ago • 1 comments

Describe the bug When I use a AuthenticatedFunctionTool, only the first call of the first user is asked for authentication. Then, the other users, on the same instance won't have to authenticate themselves. They are considered as authenticated

To Reproduce Please share a minimal code and data to reproduce your problem. Steps to reproduce the behavior:

Here the sample agent

import requests
from fastapi.openapi.models import OAuth2, OAuthFlows, OAuthFlowAuthorizationCode
from google.adk.agents.llm_agent import Agent
from google.adk.auth import AuthCredential, AuthCredentialTypes, OAuth2Auth, AuthConfig
from google.adk.auth.credential_service.session_state_credential_service import SessionStateCredentialService
from google.adk.tools import ToolContext
from google.adk.tools.authenticated_function_tool import AuthenticatedFunctionTool


YOUR_OAUTH_CLIENT_ID = "<redacted>"
YOUR_OAUTH_CLIENT_SECRET = "<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}")

    # Make a POST request on this URL
    url = f"https://<redacted>.us-central1.run.app?prompt={prompt}"
    
    headers = {}
    
    # Retrieve the credential using the context and auth config
    exchanged_credential = tool_context.get_auth_response(auth_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}")
        token = exchanged_credential.oauth2.access_token
        print(f"DEBUG: Access Token: {token[:10]}... (truncated)")
        
        headers = {"Authorization": f"Bearer {token}"}
    else:
        print("DEBUG: No credential found in tool_context")

    print(f"DEBUG: Sending request to {url}")
    print(f"DEBUG: Headers: {headers}")
    
    response = requests.get(url, headers=headers)
    print(f"DEBUG: Response: {response.text}")

auth_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": "Cloud Run"
            },
        )
    )
)

auth_credential = AuthCredential(
    auth_type=AuthCredentialTypes.OAUTH2,
    oauth2=OAuth2Auth(
        client_id=YOUR_OAUTH_CLIENT_ID,
        client_secret=YOUR_OAUTH_CLIENT_SECRET
    ),
)

auth_config = AuthConfig(
        auth_scheme=auth_scheme,
        raw_auth_credential= auth_credential
        )


authenticated_log_tool = AuthenticatedFunctionTool(func=log_tool, auth_config=auth_config, response_for_auth_required="Pending User Authorization.")

credential_service = SessionStateCredentialService()

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=[authenticated_log_tool]
)

Run the agent adk web

Then perform a first OAuth flow with a user, change the user (in the URL address bar) and ask a question. The 2nd user and the 2nd session won't have OAuth challenge

Expected behavior Have a OAuth challenge with different user, not the first one

Desktop (please complete the following information):

  • OS: Windows with WSL Unbuntu 24
  • Python version(python -V): python 3.12
  • ADK version(pip show google-adk): adk 1.19.0

Model Information:

  • Which model is being used(e.g. gemini-2.5-pro) Gemini 2.5 flash

Additional context By commenting this line and the following one, the problem disappeared. I don't understand why the exchange check is not dependent of the callback_context. Maybe the bug??

guillaumeblaquiere avatar Dec 01 '25 22:12 guillaumeblaquiere

@guillaumeblaquiere The issue is in credential_manager.py lines 186-187 where _load_existing_credential() returns the cached self._auth_config.exchanged_auth_credential without checking the current user context from callback_context.

This causes credentials from the first user to be reused for all subsequent users on the same agent instance.

The fix is exactly what you identified - removing lines 186-187 forces credential loading through the credential service (line 181), which properly respects user isolation.

PR #3773 looks good and should resolve this security issue.

Thanks!

surajksharma07 avatar Dec 02 '25 08:12 surajksharma07

@seanzhou1023 Please have a look into it.

surajksharma07 avatar Dec 03 '25 18:12 surajksharma07