Fix: request user consent for authorization-code OpenAPI tools - #862
Open
AmaadMartin wants to merge 1 commit into
Open
Fix: request user consent for authorization-code OpenAPI tools#862AmaadMartin wants to merge 1 commit into
AmaadMartin wants to merge 1 commit into
Conversation
A RestApiTool configured with an OAuth2 authorizationCode scheme and a
{clientId, clientSecret} credential never reached
Context.requestCredential(): the configured credential is truthy, so the
handler passed it straight to the exchanger, which threw because no
authorization code was present. Gate the fallback on the grant type, so a
user-interactive grant with no access token asks the client to sign in and
returns {state: 'pending'}. Two-legged clientCredentials, apiKey, http and
serviceAccount credentials keep exchanging as before.
AmaadMartin
force-pushed
the
fix/tool-auth-handler-authorization-code-consent
branch
from
August 9, 2026 12:33
9e41046 to
ec2d043
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Please ensure you have read the contribution guide before creating a pull request.
Link to Issue or Description of Change
Closes: #issue_number
Related: #issue_number
Problem: A
RestApiToolbuilt from anoauth2authorizationCodescheme can never obtain user consent.ToolAuthHandler.prepareAuthCredentials()falls back to the configured{clientId, clientSecret}, which is truthy, so it never callscontext.requestCredential()and never returnspending. It hands the token-less credential to the exchanger instead, which throwsCredentialExchangeError: clientId, clientSecret, and either authCode or authResponseUri are required for authorization code exchange.on the very first call. The static-credential fallback from Feat: Port VertexAiExampleStore example provider from adk-python #536 is correct forapiKey,http,serviceAccountandclientCredentials, but wrong for the grants that mint a token against a user's consent.Solution: A new module-level predicate
requiresUserSignIn()gates that fallback. An OAuth2/OIDC credential with noaccessToken, on a scheme whose grant type isauthorizationCode, is ignored as a source of authorization, so the handler asks the client to sign in and returns{state: 'pending'}. The second leg is unchanged:context.getAuthResponse()still wins, and the exchange-and-cache path runs as before.Behaviour source:
_external_exchange_requiredin adk-python'ssrc/google/adk/tools/openapi_tool/openapi_spec_parser/tool_auth_handler.py.Deliberate parity deviation: the Python predicate keys off the credential alone, so
clientCredentialsalso routes through a credential request there. This guard adds a second condition, the scheme's grant type. A two-leggedclientCredentialsexchange needs no human, andcore/test/tools/openapi_tool/tool_auth_handler_test.tsalready pins that. This is process-internal control flow, not a wire contract, so the local behaviour wins.Deliberate fail-open: the guard is an allow-list of one grant, so every other scheme keeps today's behaviour rather than starting to return
pending. That coversdetermineGrantType() === undefined(a bareopenIdConnectscheme, orflows: {}) and theimplicitandpasswordgrants. Those last two do need a human, butOAuth2CredentialExchangerimplements neither, andAuthHandler.generateAuthUri()always builds aresponse_type=codeURI against the authorization endpoint, falling back to the token endpoint when a flow has noauthorizationUrl. Asking the user to sign in there would replace a stateless failure with an interactive dead end, and would cache the useless credential for the rest of the session.Collision check:
gh pr liston the fork shows four overlapping open PRs, none of which lands this change: #772 (refreshes a stored credential, same file, different region), #774 (widens theauthSchemeparameter type), #845 (rebuilds this test file on real Contexts), #773 (determineGrantTypescheme guards). This PR branches frommain.Testing Plan
Please describe the tests that you ran to verify your changes. This is required for all PRs that are not small documentation or typo fixes.
Unit Tests:
[x] I have added or updated unit tests for my change.
[x] All unit tests pass locally.
Seven tests were added to
core/test/tools/openapi_tool/tool_auth_handler_test.ts. No existing test was changed. They build a realContext, sorequestCredentialmints the sign-in URI through the productionAuthHandler. New code has 100% line and branch coverage; the file reports 96.55% branch and 88.11% line because of two pre-existing untested spots,fromToolContextand the|| 'default'fallback ingetCredentialKey.Mutation testing, each run against the added tests:
credential = authResponseCredential ?? this.authCredentialasks the client to sign in for the authorization-code grantexpected 'done' to be 'pending'CLIENT_CREDENTIALSinsteadexchanges a client-credentials credential without asking the user, and the pre-existingcaches a static credential that did require an exchangeexpected 'pending' to be 'done'accessTokenearly returnuses a configured OAuth2 credential that already carries an access tokenexpected 'pending' to be 'done'authTypeearly returnuses a configured bearer credential on an authorization-code schemeAuth Scheme oauth2 requires oauth2 in authCredential.Commands run on the pushed commit:
Manual End-to-End (E2E) Tests:
Please provide instructions on how to manually test your changes, including any necessary setup or configuration.
Build an
OpenAPIToolsetfrom a spec whose security scheme isoauth2with anauthorizationCodeflow, passauthCredential: {authType: OAUTH2, oauth2: {clientId, clientSecret}}, and calltool.runAsync()once with aContextthat has afunctionCallId. No live provider is needed. I ran this script against the built package, with no mocks:Before the fix:
After the fix:
Checklist
[x] I have read the CONTRIBUTING.md document.
[x] I have performed a self-review of my own code.
[x] I have commented my code, particularly in hard-to-understand areas.
[x] I have added tests that prove my fix is effective or that my feature works.
[x] New and existing unit tests pass locally with my changes.