From 6d0f4d1f67141b908114b069ca155855d03b8fbd Mon Sep 17 00:00:00 2001 From: Anubhav Dhawan Date: Sat, 7 Feb 2026 02:48:29 +0530 Subject: [PATCH 1/3] feat(auth): Add native support for id_token in OAuth2 credentials **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 ======================== --- src/google/adk/auth/auth_credential.py | 1 + src/google/adk/auth/oauth2_credential_util.py | 18 ++++++++++-------- .../auth/test_oauth2_credential_util.py | 12 ++++++++++++ 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/google/adk/auth/auth_credential.py b/src/google/adk/auth/auth_credential.py index 6e4f73351f..e205d9be52 100644 --- a/src/google/adk/auth/auth_credential.py +++ b/src/google/adk/auth/auth_credential.py @@ -79,6 +79,7 @@ class OAuth2Auth(BaseModelWithConfig): auth_code: Optional[str] = None access_token: Optional[str] = None refresh_token: Optional[str] = None + id_token: Optional[str] = None expires_at: Optional[int] = None expires_in: Optional[int] = None audience: Optional[str] = None diff --git a/src/google/adk/auth/oauth2_credential_util.py b/src/google/adk/auth/oauth2_credential_util.py index d2f40b339f..888c8d0d39 100644 --- a/src/google/adk/auth/oauth2_credential_util.py +++ b/src/google/adk/auth/oauth2_credential_util.py @@ -107,11 +107,13 @@ def update_credential_with_tokens( auth_credential: The authentication credential to update. tokens: The OAuth2Token object containing new token information. """ - auth_credential.oauth2.access_token = tokens.get("access_token") - auth_credential.oauth2.refresh_token = tokens.get("refresh_token") - auth_credential.oauth2.expires_at = ( - int(tokens.get("expires_at")) if tokens.get("expires_at") else None - ) - auth_credential.oauth2.expires_in = ( - int(tokens.get("expires_in")) if tokens.get("expires_in") else None - ) + if auth_credential.oauth2: + auth_credential.oauth2.access_token = tokens.get("access_token") + auth_credential.oauth2.refresh_token = tokens.get("refresh_token") + auth_credential.oauth2.id_token = tokens.get("id_token") + auth_credential.oauth2.expires_at = ( + int(tokens.get("expires_at")) if tokens.get("expires_at") else None + ) + auth_credential.oauth2.expires_in = ( + int(tokens.get("expires_in")) if tokens.get("expires_in") else None + ) diff --git a/tests/unittests/auth/test_oauth2_credential_util.py b/tests/unittests/auth/test_oauth2_credential_util.py index 1e499ca741..9e2b49288e 100644 --- a/tests/unittests/auth/test_oauth2_credential_util.py +++ b/tests/unittests/auth/test_oauth2_credential_util.py @@ -222,6 +222,7 @@ def test_update_credential_with_tokens(self): tokens = OAuth2Token({ "access_token": "new_access_token", "refresh_token": "new_refresh_token", + "id_token": "new_id_token", "expires_at": expected_expires_at, "expires_in": 3600, }) @@ -230,5 +231,16 @@ def test_update_credential_with_tokens(self): assert credential.oauth2.access_token == "new_access_token" assert credential.oauth2.refresh_token == "new_refresh_token" + assert credential.oauth2.id_token == "new_id_token" assert credential.oauth2.expires_at == expected_expires_at assert credential.oauth2.expires_in == 3600 + + def test_update_credential_with_tokens_none(self): + credential = AuthCredential( + auth_type=AuthCredentialTypes.API_KEY, + ) + tokens = OAuth2Token({"access_token": "new_access_token"}) + + # Should not raise any exceptions when oauth2 is None + update_credential_with_tokens(credential, tokens) + assert credential.oauth2 is None From 778afabde4a03fea83ba59e7d38c0b4f211ac012 Mon Sep 17 00:00:00 2001 From: Anubhav Dhawan Date: Sat, 7 Feb 2026 04:39:30 +0530 Subject: [PATCH 2/3] style(auth): remove trailing whitespace for pyink formatter in test suite --- tests/unittests/auth/test_oauth2_credential_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unittests/auth/test_oauth2_credential_util.py b/tests/unittests/auth/test_oauth2_credential_util.py index 9e2b49288e..40bb471e5d 100644 --- a/tests/unittests/auth/test_oauth2_credential_util.py +++ b/tests/unittests/auth/test_oauth2_credential_util.py @@ -235,7 +235,7 @@ def test_update_credential_with_tokens(self): assert credential.oauth2.expires_at == expected_expires_at assert credential.oauth2.expires_in == 3600 - def test_update_credential_with_tokens_none(self): + def test_update_credential_with_tokens_none(self) -> None: credential = AuthCredential( auth_type=AuthCredentialTypes.API_KEY, ) From 617dfe28d1bc34a45c8d02ba92d82af67ae69a9e Mon Sep 17 00:00:00 2001 From: Anubhav Dhawan Date: Sat, 7 Feb 2026 05:28:09 +0530 Subject: [PATCH 3/3] style: fix trailing whitespace in test_oauth2_credential_util.py to satisfy pyink --- tests/unittests/auth/test_oauth2_credential_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unittests/auth/test_oauth2_credential_util.py b/tests/unittests/auth/test_oauth2_credential_util.py index 40bb471e5d..88398c482a 100644 --- a/tests/unittests/auth/test_oauth2_credential_util.py +++ b/tests/unittests/auth/test_oauth2_credential_util.py @@ -240,7 +240,7 @@ def test_update_credential_with_tokens_none(self) -> None: auth_type=AuthCredentialTypes.API_KEY, ) tokens = OAuth2Token({"access_token": "new_access_token"}) - + # Should not raise any exceptions when oauth2 is None update_credential_with_tokens(credential, tokens) assert credential.oauth2 is None