OIDC flow fails for public clients like Azure AD B2C due to mandatory client_secret using OpenAPIToolset
- Langage dominant
- Python
- Étoiles
- 21.5k
- Forks
- 4k
- Merge moyen
- 1 j 14 h
- PR mergées (30 j)
- 37
Description
***
**Is your feature request related to a problem? Please describe.**
Yes, the current OpenID Connect (OIDC) implementation requires a `client_secret` for the token exchange. This prevents integration with identity providers that handle **public clients**, such as **Azure Active Directory (AD) B2C**.
When attempting to connect with Azure AD B2C, it returns the following error because it correctly identifies the client as public and disallows a `client_secret`:
`AADB2C90084: Public clients should not send a client_secret when redeeming a publicly acquired grant.`
This makes it impossible to use the OIDC functionality with identity providers that strictly follow the OAuth 2.0 specifications for public clients.
***
**Describe the solution you'd like**
I would like the `client_secret` parameter to be **optional** instead of mandatory in the token request. This would allow the system to support the **Authorization Code Flow with PKCE (Proof Key for Code Exchange)**, which is the standard for public clients and does not use a `client_secret`.
By making this field optional, the implementation would be compatible with both:
1. **Confidential clients** that require a `client_secret`.
2. **Public clients** (like those configured in Azure AD B2C) that must not send a `client_secret`.
***
**Describe alternatives you've considered**
As a workaround, I have locally modified the source code to **disable the mandatory `client_secret` checks**. After making this change, I was able to successfully complete the authentication flow against Azure AD B2C. I received an `access_token` and was able to use it to make authorized calls to an OpenAPI-secured endpoint. This confirms that the rest of the implementation works correctly and the only blocking issue is the mandatory nature of the `client_secret`.
***
**Additional context**
This feature is crucial for broader compatibility with modern identity providers. Making the `client_secret` optional is not a deviation from security standards but rather an alignment with the official OAuth 2.0 and OpenID Connect specifications for public clients. Implementing this would significantly increase the flexibility and applicability of the tool.
****
** Example code **
```python
# Problem: The code is not working as expected, and it needs to be fixed.
# AADB2C90084 Public clients should not send a client_secret when redeeming a publicly acquired grant.
# - https://learn.microsoft.com/en-us/azure/active-directory-b2c/error-codes
# - The error is likely due to the use of a client secret in the authentication flow,
# which is not allowed for public clients in Azure AD B2C.
# - The code needs to be modified to handle the authentication flow correctly, especially for public clients
import os
import json
import httpx
from datetime import datetime
from google.adk.auth.auth_schemes import OpenIdConnectWithConfig
from google.adk.auth.auth_credential import AuthCredential, AuthCredentialTypes, OAuth2Auth
from google.adk.tools.openapi_tool.openapi_spec_parser.openapi_toolset import OpenAPIToolset
from google.adk.agents.llm_agent import LlmAgent
import logging
from dotenv import load_dotenv
load_dotenv()
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
GEMINI_MODEL = os.environ.get("GEMINI_MODEL", "gemini-2.5-flash")
OAUTH_CLIENT_ID = os.getenv("OAUTH_CLIENT_ID")
OAUTH_CLIENT_REDIRECT = os.getenv("OAUTH_CLIENT_REDIRECT")
REST_API_URL = os.getenv("REST_API_URL")
openapi_url = f"{REST_API_URL}/swagger/v1/swagger.json"
try:
response = httpx.get(openapi_url)
response.raise_for_status()
openapi_spec = response.json()
except httpx.RequestError as exc:
logger.error(f"An error occurred while requesting {exc.request.url!r}.")
openapi_spec = {}
except httpx.HTTPStatusError as exc:
logger.error(f"Error response {exc.response.status_code} while requesting {exc.request.url!r}.")
openapi_spec = {}
security_schemes = openapi_spec.get("components", {}).get("securitySchemes", {})
oauth2_scheme = security_schemes.get("oauth2", {})
implicit_flow = oauth2_scheme.get("flows", {}).get("implicit", {})
token_url = implicit_flow.get("tokenUrl")
authorization_url = implicit_flow.get("authorizationUrl", "")
scopes_dict = implicit_flow.get("scopes", {})
scopes_string = " ".join(scopes_dict.keys())
logger.info(f"Using OpenAPI spec from {openapi_url}")
logger.info(f"Token URL: {token_url}")
logger.info(f"Authorization URL: {authorization_url}")
logger.info(f"Scopes: {scopes_string}")
openapi_spec["servers"] = [
{
"url": REST_API_URL,
"description": "API Server"
}
]
auth_scheme = OpenIdConnectWithConfig(
type="openIdConnect",
authorization_endpoint=authorization_url,
token_endpoint=token_url,
scopes=scopes_dict.keys(),
)
auth_credential = AuthCredential(
auth_type=AuthCredentialTypes.OPEN_ID_CONNECT,
oauth2=OAuth2Auth(
client_id=OAUTH_CLIENT_ID,
redirect_uri=OAUTH_CLIENT_REDIRECT,
),
)
buddie_tools_to_expose = [
"api_suppport_functions_get"
]
userinfo_toolset = OpenAPIToolset(
spec_str=json.dumps(openapi_spec),
spec_str_type='json',
auth_scheme=auth_scheme,
auth_credential=auth_credential,
tool_filter=buddie_tools_to_expose,
)
root_agent = LlmAgent(
model=GEMINI_MODEL,
name='enterprise_assistant',
instruction=f"""
Help user integrate with multiple enterprise systems, including retrieving user information which may require authentication.
**Today's Date (YYYY-MM-DD):** {datetime.now().strftime("%Y-%m-%d")}
""",
tools=[userinfo_toolset],
)
```
Guide de contribution
Ouvrir le guide de contribution
Évaluation
Cette issue n'a pas encore été évaluée.