feat(keycardai-oauth): OIDC discovery fields and UserInfo capability - #232
Merged
Conversation
Types userinfo_endpoint and end_session_endpoint on AuthorizationServerMetadata and adds Client.userinfo()/AsyncClient.userinfo() per OIDC Core 1.0 Section 5.3. Implements keycardai/keycard-sdk-spec#45. Co-Authored-By: Larry Osakwe <larry@keycard.ai>
Contributor
Author
🤖 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
Contributor
There was a problem hiding this comment.
Two small asks, nothing structural. I checked the load-bearing parts: auth headers in this codebase are applied per operation, never by the transport, so the bearer-instead-of-client-auth design is sound and the assert_not_called test pins it. The discovery parser wiring for the new fields is there with both present and absent cases, and the spec's unit table is fully covered, including application/jwt rejection and no-request-on-missing-endpoint.
Co-Authored-By: Larry Osakwe <larry@keycard.ai>
Larry-Osakwe
approved these changes
Aug 24, 2026
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.
Summary
Implements keycard-sdk-spec#45 (
spec(oauth-client): OIDC discovery fields and UserInfo capability) for the Python SDK, closing the tracked divergence where these discovery fields survived only inmetadata.raw.Discovery (
AuthorizationServerMetadata) now types both fields, and absence stays non-fatal:New
operations/_userinfo.pyplusClient.userinfo()/AsyncClient.userinfo():Behavior worth calling out, matching the spec's contract table:
self._discovered_metadata, set during_ensure_initialized), or accepts pre-discovered metadata viametadata=; nouserinfoentry was added toEndpoints, so a server that doesn't advertiseuserinfo_endpointis aConfigErrorraised before any HTTP request.GETwithAccept: application/jsonandAuthorization: Bearer <access_token>. The client's own auth strategy is deliberately not applied — UserInfo authenticates the user's token, not the client — so the bearer header is written last, after context headers.UserInfoResponseexposes the validatedsubalongside the complete claims dict; nothing is filtered to a known set, so custom/provider claims pass through.401→InvalidTokenError(RFC 6750invalid_token, error code parsed from theWWW-Authenticatechallenge when present); other non-2xx →OAuthHttpError; invalid JSON, non-object body, missing/emptysub, andapplication/jwt(signed responses unsupported) →OAuthProtocolError. No caching and no POST form, per spec.Tests cover the spec's unit-test table (
tests/.../operations/test_userinfo.py), the two discovery cases (fields present / absent), and client-level wiring for both sync and async, including that no request is made when the endpoint is missing.packages/oauth/README.mddocuments the operation and the two new metadata fields.docs/sdk/*.mdxis left untouched: regenerating it withjust sdk-ref-oauthpulls in ~600 lines of unrelated drift from modules this PR doesn't touch, which belongs in its own refresh.Verification:
ruff check,pytestfor oauth (355 tests) plus starlette/mcp/a2a/langchain all pass.just typecheck(ty) isn't runnable here —tyis not declared as a dependency anywhere in the workspace and no CI job runs it;uvx ty check packages/oauth/srcreports only pre-existing diagnostic classes, with the new module'scontext.transport.request_rawunion warning identical to the existing_discovery.py/_registration.pyones.Link to Devin session: https://app.devin.ai/sessions/911c2391f4104fb591d8038487bbba56
Requested by: @Larry-Osakwe