feat: forward the OAuth2 nonce to the authorization request (v1)

Port of the same fix on main.

OAuth2Auth already has a nonce field, described as the value that binds the
user's session to the authorization request, but generate_auth_uri never put
it in the query parameters it hands to create_authorization_url. A caller who
set a nonce got an authorization request without one, so the returned id_token
could not be bound to it and the OIDC replay defence was silently absent.

The nonce is now passed through when the credential carries one, alongside the
audience parameter that is handled the same way. Flows that leave it unset are
unchanged.
This commit is contained in:
George Weale
2026-08-17 23:02:22 +00:00
parent 2277516844
commit 05337af7e1
2 changed files with 55 additions and 0 deletions
+2
View File
@@ -201,6 +201,8 @@ class AuthHandler:
}
if auth_credential.oauth2.audience:
params["audience"] = auth_credential.oauth2.audience
if auth_credential.oauth2.nonce:
params["nonce"] = auth_credential.oauth2.nonce
# If using PKCE with S256, ensure a code_verifier exists.
# If not provided in the credential, generate a cryptographically secure
+53
View File
@@ -304,6 +304,59 @@ class TestGenerateAuthUri:
assert "code_verifier" in kwargs
assert kwargs["code_verifier"] == result.oauth2.code_verifier
@patch("google.adk.auth.auth_handler.OAuth2Session")
def test_generate_auth_uri_with_nonce(
self, mock_oauth2_session, oauth2_auth_scheme, oauth2_credentials
):
"""Test that a nonce is forwarded to the authorization request."""
oauth2_credentials.oauth2.nonce = "test_nonce"
exchanged = oauth2_credentials.model_copy(deep=True)
config = AuthConfig(
auth_scheme=oauth2_auth_scheme,
raw_auth_credential=oauth2_credentials,
exchanged_auth_credential=exchanged,
)
mock_client = Mock()
mock_oauth2_session.return_value = mock_client
mock_client.create_authorization_url.return_value = (
"https://example.com/oauth2/authorize?nonce=test_nonce",
"mock_state",
)
handler = AuthHandler(config)
handler.generate_auth_uri()
_, kwargs = mock_client.create_authorization_url.call_args
assert kwargs["nonce"] == "test_nonce"
@patch("google.adk.auth.auth_handler.OAuth2Session")
def test_generate_auth_uri_without_nonce(
self, mock_oauth2_session, oauth2_auth_scheme, oauth2_credentials
):
"""Test that no nonce is sent when the credential has none."""
exchanged = oauth2_credentials.model_copy(deep=True)
config = AuthConfig(
auth_scheme=oauth2_auth_scheme,
raw_auth_credential=oauth2_credentials,
exchanged_auth_credential=exchanged,
)
mock_client = Mock()
mock_oauth2_session.return_value = mock_client
mock_client.create_authorization_url.return_value = (
"https://example.com/oauth2/authorize",
"mock_state",
)
handler = AuthHandler(config)
handler.generate_auth_uri()
_, kwargs = mock_client.create_authorization_url.call_args
assert "nonce" not in kwargs
def test_generate_auth_uri_unsupported_pkce_method(
self, oauth2_auth_scheme, oauth2_credentials
):