fix(oauth): exclude client credentials from body when Authorization header is present and _emit_control_message#877
Conversation
…eader is present Some OAuth providers (like Gong) require client credentials to be sent ONLY via Basic Authentication header, not in the request body. Previously, the CDK always included client_id and client_secret in the body, causing 400 errors when the Authorization header was also present. This change automatically detects when an Authorization header is configured in refresh_request_headers and excludes client credentials from the body. Added unit tests to verify: - Credentials excluded from body when Authorization header present - Credentials included in body when no Authorization header (default behavior) Co-Authored-By: aldo.gonzalez@airbyte.io <aldo.gonzalez@airbyte.io>
Original prompt from aldo.gonzalez@airbyte.io |
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
👋 Greetings, Airbyte Team Member!Here are some helpful tips and reminders for your convenience. Testing This CDK VersionYou can test this version of the CDK using the following: # Run the CLI from this branch:
uvx 'git+https://github.com/airbytehq/airbyte-python-cdk.git@devin/1768348770-oauth-basic-auth-header#egg=airbyte-python-cdk[dev]' --help
# Update a connector to use the CDK from this branch ref:
cd airbyte-integrations/connectors/source-example
poe use-cdk-branch devin/1768348770-oauth-basic-auth-headerHelpful ResourcesPR Slash CommandsAirbyte Maintainers can execute the following slash commands on your PR:
|
|
/prerelease |
|
/prerelease
|
…yle OAuth This adds a new configuration option to the OAuthAuthenticator that allows excluding client credentials (client_id and client_secret) from the refresh token request body entirely. Some OAuth providers like Gong have unique requirements where the token refresh endpoint only needs the refresh_token parameter and does NOT require client credentials at all. Changes: - Added use_client_credentials_in_refresh field to declarative schema - Updated abstract_oauth.py to check this option in build_refresh_request_body() - Added use_client_credentials_in_refresh() method to DeclarativeOauth2Authenticator - Updated ModelToComponentFactory to pass the option from manifest - Added unit tests for the new functionality Co-Authored-By: aldo.gonzalez@airbyte.io <aldo.gonzalez@airbyte.io>
Co-Authored-By: aldo.gonzalez@airbyte.io <aldo.gonzalez@airbyte.io>
The core fix (excluding credentials from body when Authorization header is present) is sufficient for Gong and similar OAuth providers. The additional use_client_credentials_in_refresh option was added during confusion about Gong's requirements and is no longer needed. This simplifies the implementation while maintaining the correct behavior: - When refresh_request_headers contains an Authorization header, client_id and client_secret are automatically excluded from the request body - This is required by OAuth providers like Gong that expect credentials ONLY in the Authorization header Co-Authored-By: aldo.gonzalez@airbyte.io <aldo.gonzalez@airbyte.io>
Control messages for config updates (like refreshed tokens) must be printed directly to stdout so the platform can process them immediately. Previously, when using InMemoryMessageRepository, control messages were only queued but never output to stdout, causing single-use refresh tokens to not be persisted. This fix ensures emit_configuration_as_airbyte_control_message() is always called to print to stdout, while also emitting to the message repository for any additional processing. Co-Authored-By: aldo.gonzalez@airbyte.io <aldo.gonzalez@airbyte.io>
|
/prerelease
|
airbyte_cdk/sources/declarative/models/declarative_component_schema.py
Outdated
Show resolved
Hide resolved
|
This is the connection I've been using to test https://cloud.airbyte.com/workspaces/178ce43f-7ab7-4864-8c76-07d666a203df/connections/6729d3f8-4a30-462b-9e5e-891b378b06da/timeline |
Co-Authored-By: aldo.gonzalez@airbyte.io <aldo.gonzalez@airbyte.io>
Brian Lai (brianjlai)
left a comment
There was a problem hiding this comment.
I mainly have some questions around the part that aldo very sensibly also pointed out in the PR. The main thing we should confirm is what happens when the platform receives the CONTROL message. Once we have that confirmed, can ✅
Control Message Flow for Single-Use Refresh TokensThis PR fixes a critical issue with single-use refresh tokens (like Gong's). Here's a detailed explanation of the behavior for future reference. The ProblemWhen a connector refreshes OAuth tokens, it receives a new refresh token from the OAuth provider. This new token must be persisted by the platform, otherwise subsequent operations will fail because the old refresh token is now invalid. The bug: Control messages were being queued in Flow DiagramsManual Setup Flow (WITHOUT fix) - FAILSManual Setup Flow (WITH fix) - WORKSOAuth Flow TimingThe OAuth flow (via "Authenticate" button) delays this issue because:
Key Technical Details
Testing EvidenceSide-by-side testing confirmed:
|
Summary
Some OAuth providers (like Gong) require client credentials to be sent ONLY via Basic Authentication header, not in the request body. Previously, the CDK always included
client_idandclient_secretin the token refresh request body, causing 400 errors when an Authorization header was also configured.This change modifies
build_refresh_request_body()to automatically detect when anAuthorizationheader is present inrefresh_request_headersand excludes client credentials from the body in that case.Key changes:
Authorizationheader presenceUpdates since last revision
use_client_credentials_in_refreshoption that was added during initial implementation. After confirming Gong's actual requirements (credentials via Basic auth header), this option was unnecessary complexity.refresh_request_headers._emit_control_message()inSingleUseRefreshTokenOauth2Authenticatorto always print control messages to stdout. Previously, when usingInMemoryMessageRepository, control messages were only queued but never output to stdout, causing the platform to never receive config updates. This meant single-use refresh tokens were not being persisted after refresh, causing subsequent operations to fail with "Refresh token is invalid or expired" errors.declarative_component_schema.py(copyright header removal,use_cachedescription update) that were introduced by running build commands.Review & Testing Checklist for Human
_emit_control_message()change now always emits to stdout AND to the message repository. Verify this doesn't cause duplicate processing or other side effects.refresh_request_headersor without anAuthorizationheader should continue to work unchanged (credentials in body)"Authorization" in headersassumes any Authorization header means credentials are in the header. Consider if there are edge cases where this assumption is wrong (e.g., Bearer tokens)Recommended test plan:
connectorConfigurationUpdated: truein the responseNotes
This PR is part of implementing OAuth support for source-gong connector (airbyte#71356). The Gong API was returning 400 errors because it received credentials in both the Basic auth header AND the request body. Additionally, Gong uses single-use refresh tokens, which exposed a bug where control messages weren't being emitted to stdout.
Requested by: @aldogonzalez-airbyte (aldo.gonzalez@airbyte.io)
Link to Devin run: https://app.devin.ai/sessions/bac4e6c3e7684fd1802811a5c8c186a1
Important
Auto-merge enabled.
This PR is set to merge automatically when all requirements are met.