Gate inbound DCR (/oauth/register) behind an initial access token
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 2.2k
- Forks
- 300
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 184
Description
Problem
RegisterClientHandler (pkg/authserver/server/handlers/dcr.go, added in #3428) has no authentication or authorization check on the inbound /oauth/register endpoint. Whether a caller can register a client is controlled only by two booleans, AllowConfidentialClientRegistration and AllowPrivateKeyJWTRegistration — not by who's calling. When either is enabled, any caller who can reach the endpoint can self-register a client and immediately authenticate as it: a confidential client gets an unattested secret handed back, and a private_key_jwt client just submits its own public key and proves possession of the matching private key it generated itself.
Neither case is proof that the caller is a legitimate or trusted party — it only proves "the same caller as last time." BaselineClientScopes limits what a self-registered client can do, but that's a separate, additive control, not a substitute for gating the registration call itself. Left open, this lets anyone farm the endpoint for unlimited client creation (credential-stuffing surface, storage/audit-log growth, and a foothold for any future bug in scope logic).
Why this matters now
Comparing against how mature authorization servers handle this:
- RFC 7591 §3 defines the initial-access-token mechanism specifically to let an AS "limit registration to only previously authorized parties." Separately, §2.3 defines software statements for the same purpose.
- Okta, Auth0, and Keycloak all default DCR to closed, requiring an initial access token or a trusted software statement.
- Entra ID goes further: there's no anonymous self-registration concept at all. App creation always requires an authenticated principal, and key/cert association is always performed by an already-authenticated owner/admin, never inline at registration.
ToolHive's own codebase already has the right building blocks — InitialAccessTokenFile/InitialAccessTokenEnvVar/SoftwareStatement (pkg/authserver/config.go) — but they're wired only for the outbound direction (ToolHive as a client doing DCR against an upstream AS). The inbound /oauth/register endpoint has no equivalent.
Proposed fix
Add an optional InitialAccessToken gate to the inbound DCR path, reusing the existing RFC 7591 §3 pattern already present for outbound DCR:
- When configured,
RegisterClientHandlerrequires a valid bearer token (the initial access token) before accepting a registration request. - When unset, behavior is unchanged (opt-in, no behavior change for existing deployments).
- Consider whether this should also support a trusted software-statement path as a middle ground for deployments that want open self-registration without a shared bearer token.
Scope
This is pre-existing and affects both AllowConfidentialClientRegistration and AllowPrivateKeyJWTRegistration equally — it's not specific to either auth method. It surfaced during review of the private_key_jwt DCR work but is not a regression from that change and is not a blocker for it.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with RegisterClientHandler in pkg/authserver/server/handlers/dcr.go and inspect the existing InitialAccessTokenFile, InitialAccessTokenEnvVar, and SoftwareStatement configuration in pkg/authserver/config.go. Compare the inbound path with the existing outbound DCR initial-access-token handling, then define and test bearer-token validation for configured deployments while preserving current behavior when unset. Resolve whether software statements belong in this change before implementation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- api, authentication, backend, security
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100