feat: add OIDC Dynamic Client Registration (RFC 7591/7592) - #2
Open
jfroy wants to merge 17 commits into
Open
Conversation
* feat: add OAuth Client ID Metadata Document support Implement the protocol-level building blocks for OAuth Client ID Metadata Documents. See https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document for the draft specification. * move more logic from pocket id into fosite * hardening based on claude's feedack --------- Co-authored-by: Elias Schneider <login@eliasschneider.com>
jfroy
force-pushed
the
mcp
branch
3 times, most recently
from
August 7, 2026 15:47
7501083 to
3ea4c21
Compare
Closed
2 tasks
An authorization server fetches several client-supplied URLs: a client ID metadata document, a logo_uri, a JWKS URI. Each needs the same protection, so export the guard the CIMD fetcher already used instead of leaving every caller to rebuild it. SSRFGuardedTransport hardens a transport so requests cannot reach loopback, private, link-local or other special-use addresses. A pre-flight DNS check alone is insufficient, because a name can resolve to a public address when it is validated and an internal one when the connection is made. The dialer's Control hook runs after resolution on the concrete address, closing that window on the initial request and on every redirect hop. It also clears three settings that would otherwise defeat the check: - Proxy, because the dialer would connect to the proxy while the proxy reaches the real destination, so only the proxy's address would ever be inspected - DialTLS and DialTLSContext, because they bypass DialContext entirely Because the guard works by owning the dialer, it can only be applied to an *http.Transport. A base of any other type, such as an instrumentation wrapper, hides the transport that actually dials and cannot be guarded. Returning it unchanged would hand back something that looks guarded and is not, so the guard fails closed and derives a fresh transport instead; wrappers belong around the result rather than underneath it. A round tripper that genuinely never dials, such as an in-memory test double, opts out explicitly via SSRFGuardExempt. The CIMD fetcher now uses the shared implementation, so its behaviour is unchanged and there is a single place to harden.
Add the pieces of dynamic client registration that are defined entirely by the spec, so an authorization server implementing RFC 7591 does not have to redeclare them. Message types: ClientRegistrationRequest and ClientRegistrationResponse model the client information request and response (RFC 7591 sections 2 and 3.2.1), which are also the bodies of an RFC 7592 update. These mirror the existing ClientMetadataDocument, whose members are nearly identical because a client ID metadata document carries the same metadata a registration request does. Only the members Fosite has an opinion about are modelled; per section 3.1 a server must ignore metadata it does not understand rather than reject the request. Validation: ValidateRegistrationRedirectURIs enforces the redirect_uri rules on a registration request. Because the request comes from an untrusted caller, these hold regardless of any deployment-specific allowlist: - at least one redirect_uri must be present - each must be an absolute URI with no fragment (RFC 6749 section 3.1.2) - each must not use an active-content scheme IsActiveContentRedirectURI rejects javascript, vbscript, data, blob, file, about and filesystem. Redirecting to one of these executes script in the browsing context performing the redirect, or exposes attacker-controlled inline or local content, so it is an XSS or local-file-disclosure primitive rather than a real callback. This matters most for dynamic registration, where an operator allowlist may legitimately contain a scheme wildcard and would otherwise match them. Private-use schemes stay valid so native apps following RFC 8252 can register. Failures use the RFC 7591 invalid_redirect_uri error code, which Fosite did not previously define. client_secret_expires_at is modelled as a pointer. RFC 7591 section 3.2.1 requires it whenever a client_secret is issued, and 0 (the secret never expires) is exactly the value a plain omitempty int would drop from the response.
stonith404
force-pushed
the
main
branch
3 times, most recently
from
August 12, 2026 21:14
5c9132d to
82908df
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.
What does this PR do?
This PR supports pocket-id/pocket-id#1672 which adds Dynamic Client Registration (RFC 7591/7592) and OAuth 2.0 Authorization Server Metadata (RFC 8414), with the goal of supporting popular cloud-based MCP client registration for MCP servers using OIDC (either directly or via a proxy like ToolHive).