Skip to content

Concur OAuth2 code exchange fails in ADK: 'str' object has no attribute 'get' (client_secret_post) #4473

@YutaMiyake

Description

@YutaMiyake

🔴 Required Information

Describe the Bug:
When using SAP Concur OAuth2 (authorization code flow) with
token_endpoint_auth_method="client_secret_post", ADK fails to exchange the
authorization code to Concur and logs:

Failed to exchange authorization code: 'str' object has no attribute 'get'

This indicates ADK is receiving a string where it expects a token dict.

Steps to Reproduce:

  1. Configure Concur OAuth2 with token_endpoint_auth_method="client_secret_post".
  2. Complete the authorize flow and receive code from Concur.
  3. Let ADK exchange the code.
  4. Observe the error above.

Expected Behavior:
ADK should successfully exchange the authorization code and store tokens
(access_token, refresh_token, expires_in, etc).

Proposed Fix:
When token_endpoint_auth_method == "client_secret_post", do not pass
client_id explicitly to fetch_token(). Let Authlib include it in the POST
body. Keep existing behavior for client_secret_basic.

Observed Behavior:

Environment Details:

  • ADK Library Version (pip show google-adk): 1.24.0
  • Desktop OS:** N/A (server environment)
  • Python Version (python -V): Python 3.11.13

Model Information:

  • Are you using LiteLLM: No
  • Which model is being used: N/A

🟡 Optional Information

Providing this information greatly speeds up the resolution process.

Regression:

Additional Context:
We can reproduce a related issue using a minimal Authlib OAuth2Session script:
when token_endpoint_auth_method="client_secret_post" and client_id is passed
explicitly to fetch_token(), Concur returns:

"Received vectorized value :client_id=[\"<id>\" \"<id>\"]"

This suggests that if ADK passes client_id while using client_secret_post,
it may cause unexpected token responses or parsing issues.

Minimal Reproduction Code:

from authlib.integrations.requests_client import OAuth2Session

session = OAuth2Session(
    client_id="<id>",
    client_secret="<secret>",
    token_endpoint="https://glz.api.concursolutions.com/oauth2/v0/token",
    token_endpoint_auth_method="client_secret_post",
)

# Passing client_id here duplicates it in the POST body:
tokens = session.fetch_token(
    "https://glz.api.concursolutions.com/oauth2/v0/token",
    code="<auth_code>",
    grant_type="authorization_code",
    redirect_uri="<redirect_uri>",
    client_id="<id>",
)

How often has this issue occurred?:

  • Always (100%)

Metadata

Metadata

Assignees

No one assigned

    Labels

    auth[Component] This issue is related to authorization

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions