feat(auth): Add native support for id_token in OAuth2 credentials#4402
feat(auth): Add native support for id_token in OAuth2 credentials#4402anubhav756 wants to merge 3 commits intogoogle:mainfrom
Conversation
Summary of ChangesHello @anubhav756, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request enhances the authentication system by introducing native support for 'id_token' within OAuth2 credentials. Previously, 'id_token' values, crucial for user identity verification in OpenID Connect flows, were dropped. The changes ensure that these tokens are now correctly captured and stored, enabling downstream components to securely verify user identity and extract OIDC claims. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces support for id_token within the OAuth2 authentication flow, which is a valuable addition for OpenID Connect scenarios. The changes are implemented cleanly and consistently. The OAuth2Auth model is correctly updated to include the optional id_token field, the update_credential_with_tokens utility is properly modified to handle the new token, and the unit tests are expanded to verify this new behavior. The implementation is straightforward and correct.
…ow crashes
### Overview
This PR addresses several authentication bugs within the `toolbox-adk` wrapper that were preventing successful OIDC flows, causing repeated login prompts, and crashing during tool execution.
## Changes
### 1. ID Token Retention (Temporary Monkey Patch)
#### Problem
`adk-python` natively drops the `id_token` during OAuth2 exchange (`update_credential_with_tokens`), which is strictly required by the MCP Toolbox for `USER_IDENTITY` authentication.
#### Solution
Added a temporary monkey patch to hook into the `adk-python` credential update process and explicitly preserve `tokens.get("id_token")`.
> [!NOTE]
> Tracked by `TODO`s to remove once upstream PR google/adk-python#4402 is merged.
### 2. Credential Persistence Fix
#### Problem
Tools repeatedly prompted users to authenticate because the exchanged credentials were not being appropriately persisted across sessions (`exchanged_auth_credential` was left unpopulated/None).
#### Solution
Explicitly assigned the newly fetched user credentials to `auth_config_adk.exchanged_auth_credential` and synced them to storage using `tool_context._invocation_context.credential_service.save_credential()`.
### 3. Safe Default OIDC Scopes
#### Problem
Passing an empty list of scopes triggered an `AttributeError` deep inside the `adk-python` `auth_handler.py` due to a falsy chain evaluation on empty dictionaries.
#### Solution
Added default OIDC fallback scopes (`["openid", "profile", "email"]`) for `USER_IDENTITY` flows when no explicit scopes are provided.
### 4. Auth Token Getter Deduplication
#### Problem
If a tool required the same authentication service across multiple parameters, the `add_auth_token_getter` logic would attempt to register it twice, causing a `ValueError: already registered`.
#### Solution
Collected `needed_services` into a deduplicated `set` and added a protective check before registration (`if not hasattr(...) or s not in ...`) to ensure idempotency.
### Testing
- [x] Verified that submitting empty scopes successfully defaults to `openid profile email` and resolves without crashing `adk-python`.
- [x] Verified that the `id_token` is successfully propagated bounds and retrieved via `getattr(creds.oauth2, "id_token", ...)` during tool execution.
- [x] Verified that subsequent tool executions load the saved credentials seamlessly from the `CredentialService` without triggering continuous Google OAuth consent screens.
…ow crashes
This PR addresses several authentication bugs within the `toolbox-adk` wrapper that were preventing successful OIDC flows, causing repeated login prompts, and crashing during tool execution.
`adk-python` natively drops the `id_token` during OAuth2 exchange (`update_credential_with_tokens`), which is strictly required by the MCP Toolbox for `USER_IDENTITY` authentication.
#### Solution
Added a temporary monkey patch to hook into the `adk-python` credential update process and explicitly preserve `tokens.get("id_token")`.
> [!NOTE]
> Tracked by `TODO`s to remove once upstream PR google/adk-python#4402 is merged.
Tools repeatedly prompted users to authenticate because the exchanged credentials were not being appropriately persisted across sessions (`exchanged_auth_credential` was left unpopulated/None).
Explicitly assigned the newly fetched user credentials to `auth_config_adk.exchanged_auth_credential` and synced them to storage using `tool_context._invocation_context.credential_service.save_credential()`.
Passing an empty list of scopes triggered an `AttributeError` deep inside the `adk-python` `auth_handler.py` due to a falsy chain evaluation on empty dictionaries.
Added default OIDC fallback scopes (`["openid", "profile", "email"]`) for `USER_IDENTITY` flows when no explicit scopes are provided.
If a tool required the same authentication service across multiple parameters, the `add_auth_token_getter` logic would attempt to register it twice, causing a `ValueError: already registered`.
Collected `needed_services` into a deduplicated `set` and added a protective check before registration (`if not hasattr(...) or s not in ...`) to ensure idempotency.
- [x] Verified that submitting empty scopes successfully defaults to `openid profile email` and resolves without crashing `adk-python`.
- [x] Verified that the `id_token` is successfully propagated bounds and retrieved via `getattr(creds.oauth2, "id_token", ...)` during tool execution.
- [x] Verified that subsequent tool executions load the saved credentials seamlessly from the `CredentialService` without triggering continuous Google OAuth consent screens.
**Please ensure you have read the [contribution guide](https://github.com/google/adk-python/blob/main/CONTRIBUTING.md) before creating a pull request.** **2. Or, if no issue exists, describe the change:** When performing authentication flows via `OAUTH2` or `OPEN_ID_CONNECT`, the native `OAuth2Token` response from identity providers (like Google OAuth) often includes an `id_token` alongside the `access_token` and `refresh_token`. However, the ADK's `update_credential_with_tokens` utility explicitly drops the `id_token`, preventing agents and tools from verifying user identity or extracting OIDC claims securely. Furthermore, the `OAuth2Auth` model does not have a designated field for `id_token`. 1. Added an `id_token: Optional[str] = None` field to the `OAuth2Auth` pydantic model in `auth_credential.py`. 2. Updated `update_credential_with_tokens` in `oauth2_credential_util.py` to correctly extract and map `tokens.get("id_token")` into the `OAuth2Auth` credential object. 3. Updated the relevant unit tests to ensure `id_token` is asserted and preserved during credential updates. **Unit Tests:** - [x] I have added or updated unit tests for my change. - [x] All unit tests pass locally. Summary of passed `pytest` results: ```bash $ pytest tests/unittests/auth/test_oauth2_credential_util.py ======================= test session starts ======================= platform darwin -- Python 3.11.9, pytest-9.0.1, pluggy-1.6.0 collected 9 items tests/unittests/auth/test_oauth2_credential_util.py ......... [100%] ======================== 9 passed in 0.05s ========================
2353353 to
6d0f4d1
Compare
…ow crashes
This PR addresses several authentication bugs within the `toolbox-adk` wrapper that were preventing successful OIDC flows, causing repeated login prompts, and crashing during tool execution.
`adk-python` natively drops the `id_token` during OAuth2 exchange (`update_credential_with_tokens`), which is strictly required by the MCP Toolbox for `USER_IDENTITY` authentication.
#### Solution
Added a temporary monkey patch to hook into the `adk-python` credential update process and explicitly preserve `tokens.get("id_token")`.
> [!NOTE]
> Tracked by `TODO`s to remove once upstream PR google/adk-python#4402 is merged.
Tools repeatedly prompted users to authenticate because the exchanged credentials were not being appropriately persisted across sessions (`exchanged_auth_credential` was left unpopulated/None).
Explicitly assigned the newly fetched user credentials to `auth_config_adk.exchanged_auth_credential` and synced them to storage using `tool_context._invocation_context.credential_service.save_credential()`.
Passing an empty list of scopes triggered an `AttributeError` deep inside the `adk-python` `auth_handler.py` due to a falsy chain evaluation on empty dictionaries.
Added default OIDC fallback scopes (`["openid", "profile", "email"]`) for `USER_IDENTITY` flows when no explicit scopes are provided.
If a tool required the same authentication service across multiple parameters, the `add_auth_token_getter` logic would attempt to register it twice, causing a `ValueError: already registered`.
Collected `needed_services` into a deduplicated `set` and added a protective check before registration (`if not hasattr(...) or s not in ...`) to ensure idempotency.
- [x] Verified that submitting empty scopes successfully defaults to `openid profile email` and resolves without crashing `adk-python`.
- [x] Verified that the `id_token` is successfully propagated bounds and retrieved via `getattr(creds.oauth2, "id_token", ...)` during tool execution.
- [x] Verified that subsequent tool executions load the saved credentials seamlessly from the `CredentialService` without triggering continuous Google OAuth consent screens.
…ow crashes
This PR addresses several authentication bugs within the `toolbox-adk` wrapper that were preventing successful OIDC flows, causing repeated login prompts, and crashing during tool execution.
`adk-python` natively drops the `id_token` during OAuth2 exchange (`update_credential_with_tokens`), which is strictly required by the MCP Toolbox for `USER_IDENTITY` authentication.
#### Solution
Added a temporary monkey patch to hook into the `adk-python` credential update process and explicitly preserve `tokens.get("id_token")`.
> [!NOTE]
> Tracked by `TODO`s to remove once upstream PR google/adk-python#4402 is merged.
Tools repeatedly prompted users to authenticate because the exchanged credentials were not being appropriately persisted across sessions (`exchanged_auth_credential` was left unpopulated/None).
Explicitly assigned the newly fetched user credentials to `auth_config_adk.exchanged_auth_credential` and synced them to storage using `tool_context._invocation_context.credential_service.save_credential()`.
Passing an empty list of scopes triggered an `AttributeError` deep inside the `adk-python` `auth_handler.py` due to a falsy chain evaluation on empty dictionaries.
Added default OIDC fallback scopes (`["openid", "profile", "email"]`) for `USER_IDENTITY` flows when no explicit scopes are provided.
If a tool required the same authentication service across multiple parameters, the `add_auth_token_getter` logic would attempt to register it twice, causing a `ValueError: already registered`.
Collected `needed_services` into a deduplicated `set` and added a protective check before registration (`if not hasattr(...) or s not in ...`) to ensure idempotency.
- [x] Verified that submitting empty scopes successfully defaults to `openid profile email` and resolves without crashing `adk-python`.
- [x] Verified that the `id_token` is successfully propagated bounds and retrieved via `getattr(creds.oauth2, "id_token", ...)` during tool execution.
- [x] Verified that subsequent tool executions load the saved credentials seamlessly from the `CredentialService` without triggering continuous Google OAuth consent screens.
Please ensure you have read the contribution guide before creating a pull request.
Link to Issue or Description of Change
2. Or, if no issue exists, describe the change:
Problem
When performing authentication flows via
OAUTH2orOPEN_ID_CONNECT, the nativeOAuth2Tokenresponse from identity providers (like Google OAuth) often includes anid_tokenalongside theaccess_tokenandrefresh_token. However, the ADK'supdate_credential_with_tokensutility explicitly drops theid_token, preventing agents and tools from verifying user identity or extracting OIDC claims securely. Furthermore, theOAuth2Authmodel does not have a designated field forid_token.Solution
id_token: Optional[str] = Nonefield to theOAuth2Authpydantic model inauth_credential.py.update_credential_with_tokensinoauth2_credential_util.pyto correctly extract and maptokens.get("id_token")into theOAuth2Authcredential object.id_tokenis asserted and preserved during credential updates.Testing Plan
Unit Tests:
Summary of passed
pytestresults: