Skip to content

Add OAuth/OIDC authentication for remote MCP server registries#3908

Merged
ChrisJBurns merged 12 commits intomainfrom
cli-registry-auth
Mar 11, 2026
Merged

Add OAuth/OIDC authentication for remote MCP server registries#3908
ChrisJBurns merged 12 commits intomainfrom
cli-registry-auth

Conversation

@ChrisJBurns
Copy link
Copy Markdown
Collaborator

@ChrisJBurns ChrisJBurns commented Feb 20, 2026

Summary

Organizations deploying private MCP server registries behind identity providers
(Okta, Azure AD, etc.) need a way to attach credentials to registry API calls.
This implements RFC-0043 Phase 1: browser-based OAuth/OIDC authentication with
PKCE for the ToolHive CLI.

  • Add pkg/registry/auth/ package with TokenSource interface, OAuth token
    source (three-stage: in-memory cache → secrets manager restore → browser flow),
    and HTTP transport wrapper for Bearer token injection
  • Add RegistryAuth / RegistryOAuthConfig config structs with mandatory PKCE
    (S256) — no opt-out flag, no client secret (public clients only)
  • Add DeriveSecretKey using sha256(registryURL + "\x00" + issuer)[:4] for
    unique per-registry secret storage keys
  • Add WithInteractive() functional option to NewRegistryProvider so
    thv serve (headless) returns ErrRegistryAuthRequired instead of blocking
    on a browser flow
  • Add AuthManager business logic and CLI commands:
    thv config set-registry-auth / unset-registry-auth
  • Thread token source through factory → cached provider → API provider → HTTP
    client; skip validation probe when auth is configured
  • Default scopes include offline_access so providers return refresh tokens
  • Debug logging throughout token persistence flow for diagnostics

Relates to https://github.com/stacklok/toolhive-rfcs/blob/main/rfcs/THV-0043-registry-authentication.md

Changes

File Change
pkg/config/config.go Add RegistryAuth, RegistryOAuthConfig structs, RegistryAuthTypeOAuth constant
pkg/registry/auth/auth.go TokenSource interface, NewTokenSource factory, DeriveSecretKey, ErrRegistryAuthRequired
pkg/registry/auth/oauth_token_source.go OAuth token source: cache → restore → browser flow, token persistence, debug logging
pkg/registry/auth/transport.go HTTP RoundTripper wrapper injecting Bearer tokens
pkg/registry/auth_manager.go AuthManager interface with OIDC discovery validation
pkg/registry/factory.go WithInteractive option, resolveTokenSource() with secrets degradation logging
pkg/registry/provider_api.go Token source threading, skip validation probe when auth configured
pkg/registry/provider_cached.go Pass-through token source parameter
pkg/registry/api/client.go Wrap HTTP transport with auth.WrapTransport
cmd/thv/app/config_registryauth.go thv config set-registry-auth / unset-registry-auth commands
pkg/registry/auth/auth_test.go Key derivation, token source, cache restore (with httptest OIDC), persister tests
pkg/registry/auth/transport_test.go Transport wrapping, token injection, request immutability tests
pkg/registry/auth_manager_test.go AuthManager UnsetAuth and GetAuthInfo tests with gomock

Does this introduce a user-facing change?

Yes. Two new CLI commands:

  • thv config set-registry-auth --issuer <url> --client-id <id> [--scopes <list>] [--audience <value>] — configure OAuth for a remote registry
  • thv config unset-registry-auth — remove registry auth configuration

When a registry API URL is configured with auth, the CLI automatically handles
token acquisition via browser-based OAuth, with refresh tokens cached in the
secrets manager for subsequent invocations.

Special notes for reviewers

  • PKCE is mandatory — no --use-pkce flag; always S256 per OAuth 2.1
  • Public clients only — no ClientSecret field; PKCE replaces it
  • Validation probe skip: When auth is configured, the startup probe
    (ListServers with limit=1) is skipped because it would trigger a browser flow
    before the user intends to use the registry
  • Secrets degradation: If the secrets manager is not set up, tokens work
    in-memory only for the current session — no persistence, but no failure
  • Phase 1 only: thv registry login/logout, API auth status fields, and
    HTTP 401/403 detection are planned for follow-up PRs per RFC-0043

Large PR Justification

  • code changes are what allows the CLI to work with a private registry

🤖 Generated with Claude Code

@github-actions github-actions bot added the size/L Large PR: 600-999 lines changed label Feb 20, 2026
@github-actions github-actions bot added size/L Large PR: 600-999 lines changed and removed size/L Large PR: 600-999 lines changed labels Mar 9, 2026
@ChrisJBurns ChrisJBurns changed the title DRAFT: CLI auth reg Add OAuth/OIDC authentication for remote registries Mar 9, 2026
@github-actions github-actions bot added size/XL Extra large PR: 1000+ lines changed and removed size/L Large PR: 600-999 lines changed labels Mar 9, 2026
Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Large PR Detected

This PR exceeds 1000 lines of changes and requires justification before it can be reviewed.

How to unblock this PR:

Add a section to your PR description with the following format:

## Large PR Justification

[Explain why this PR must be large, such as:]
- Generated code that cannot be split
- Large refactoring that must be atomic
- Multiple related changes that would break if separated
- Migration or data transformation

Alternative:

Consider splitting this PR into smaller, focused changes (< 1000 lines each) for easier review and reduced risk.

See our Contributing Guidelines for more details.


This review will be automatically dismissed once you add the justification section.

@github-actions github-actions bot added size/XL Extra large PR: 1000+ lines changed and removed size/XL Extra large PR: 1000+ lines changed labels Mar 9, 2026
@github-actions github-actions bot added the size/XL Extra large PR: 1000+ lines changed label Mar 9, 2026
Signed-off-by: Chris Burns <29541485+ChrisJBurns@users.noreply.github.com>
@github-actions github-actions bot added size/XL Extra large PR: 1000+ lines changed and removed size/XL Extra large PR: 1000+ lines changed labels Mar 9, 2026
@github-actions github-actions bot added size/XL Extra large PR: 1000+ lines changed and removed size/XL Extra large PR: 1000+ lines changed labels Mar 10, 2026
@github-actions github-actions bot added size/XL Extra large PR: 1000+ lines changed and removed size/XL Extra large PR: 1000+ lines changed labels Mar 10, 2026
@github-actions github-actions bot added size/XL Extra large PR: 1000+ lines changed and removed size/XL Extra large PR: 1000+ lines changed labels Mar 10, 2026
@github-actions github-actions bot added size/XL Extra large PR: 1000+ lines changed and removed size/XL Extra large PR: 1000+ lines changed labels Mar 10, 2026
@github-actions github-actions bot added size/XL Extra large PR: 1000+ lines changed and removed size/XL Extra large PR: 1000+ lines changed labels Mar 10, 2026
@github-actions github-actions bot added size/XL Extra large PR: 1000+ lines changed and removed size/XL Extra large PR: 1000+ lines changed labels Mar 10, 2026
@github-actions github-actions bot added size/XL Extra large PR: 1000+ lines changed and removed size/XL Extra large PR: 1000+ lines changed labels Mar 11, 2026
Copy link
Copy Markdown
Contributor

@jhrozek jhrozek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one Johnny-came-late comment inline

return fmt.Errorf("failed to create oauth2 config: %w", err)
}

o.tokenSource = remote.CreateTokenSourceFromCached(oauth2Cfg, refreshToken, o.oauthCfg.CachedTokenExpiry)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the returned token source here be wrapped PersistingTokenSource? The IDP would return a rotated refresh token here. perfromOAuthFlow does that correctly already

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/XL Extra large PR: 1000+ lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants