Uh oh!
There was an error while loading. Please reload this page.
feat(keycardai-oauth): client_id form parameter on the client-credentials grant - #238
Conversation
…ials grant Co-Authored-By: Larry Osakwe <larry@keycard.ai>
🤖 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:
|
Larry-Osakwe
left a comment
There was a problem hiding this comment.
Verified the wire behavior is carried by the model_dump(exclude_none=True) encoding shared with token exchange, and the exact-match form assertions pin both the present-when-set and absent-when-None behaviors, so no parallel encoding path was the right call.
On the open questions: no higher-level helper needed, the langchain follow-up is that layer (_client_auth_fields already lifts from the prepared request, which carries client_id from WorkloadIdentity; it just could not forward it until now). Repo-wide ruff format is a real but separate decision, since a format-only pass churns blame across the package; not blocking here.
Uh oh!
There was an error while loading. Please reload this page.
Summary
ECO-306.
ClientCredentialsRequestcarriedclient_assertion/client_assertion_typebut noclient_id, so a federation-rule workload identity credential could not perform an as-itself grant: token federation application credentials are resolved by application ID, not by the assertion subject.TokenExchangeRequestalready has the field (WorkloadIdentity.prepare_token_exchange_requestsets it), which is the divergence recorded inkeycard-sdk-specspecs/delegated-access/as-itself.mdand the gate on ECO-240. RFC 7521 section 4.2 permitsclient_idalongside assertion-based client authentication.class ClientCredentialsRequest(BaseModel): client_assertion: str | None = None client_assertion_type: str | None = None + client_id: str | None = Field(default=None, description=...) timeout: float | None = NoneWire encoding needed no change:
build_client_credentials_http_requestform-encodesrequest.model_dump(exclude_none=True, exclude={"timeout"}), identical tobuild_token_exchange_http_request. So the key isclient_id, and it is omitted entirely when unset, matching token exchange by construction rather than by a parallel code path. Tests pin both behaviors so a future change to the encoder cannot silently drop or emit the key.client_idis caller-supplied only; nothing derives it from the auth strategy. Added to the kwargs overloads and docstrings ofAsyncClient.client_credentials_grantandClient.client_credentials_grant.Scope is
packages/oauthonly: release automation keys off the squash commit's single package scope, so thepackages/langchainlift ofclient_idinto_client_auth_fieldsis a follow-up PR after this releases, with akeycardai-oauthfloor bump there. The spec divergence-row flip is handled separately.Docs
Did not regenerate
docs/sdk/*.mdx.mdxifyemits class and function docstrings, not pydantic field lists, and the generatedkeycardai-oauth-types-models.mdx/keycardai-oauth-client.mdxcurrently contain neitherClientCredentialsRequestnorclient_credentials_grant, so this change produces no docs delta. Regenerating would instead sweep in unrelated churn (stale source line anchors) from drift that predates this branch.Verification
packages/oauthsuite passes (387 tests) viacd packages/oauth && uv run --extra test pytest tests/(justis not installed on this box; this is the recipe body ofjust test-package oauth).uv run ruff checkpasses.uv run ruff formatis not run: the repo is not currently format-clean, and it rewrites 152 files including untouched packages.packages/oauth/src/keycardai/oauth/types/models.pywas already non-format-clean onmain. No formatting-only changes are included here.Open questions
client_idalso be accepted by the higher-level as-itself helpers, or is the raw grant enough until the langchain follow-up lands?ruff formatintended to be enforced repo-wide? A separate format-only pass would makejustverification match the house rules.Link to Devin session: https://app.devin.ai/sessions/bb57fb9373a747ab841119ed381ff9fa
Requested by: @Larry-Osakwe