Add OAuth support for claude.ai custom connectors - #3
Open
ryudoawaru wants to merge 1 commit into
Open
Conversation
claude.ai only accepts public HTTPS + OAuth for custom connectors; it cannot send a static Authorization header. Accept Redmine's built-in doorkeeper tokens (Redmine/RedMica 6.1+) and advertise the OAuth server so clients can discover it. - McpController#authenticate_mcp_user! tries Doorkeeper.authenticate first, setting oauth_scope like Redmine core does, then falls back to the static API key path used by Claude Code / Claude Desktop. - 401 responses carry WWW-Authenticate with the resource metadata URL. - New unauthenticated discovery endpoints: RFC 9728 protected resource metadata and RFC 8414 authorization server metadata, pointing at doorkeeper's /oauth/authorize and /oauth/token. - Advertise scopes_supported: doorkeeper's default_scopes only covers public permissions, so a client that requests no scope gets denied by every tool. - Disable the transport's DNS rebinding protection: its Host allowlist is loopback-only and 403s any real deployment. Rails' config.hosts already does host authorization. - Drop POST /mcp/token — obsolete with OAuth, and it was an unthrottled password brute-force surface. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Why
claude.ai custom connectors only accept public HTTPS + OAuth — they can't send a static
Authorizationheader. This plugin authenticated with a fixed Redmine API key, so claude.ai could never connect.Redmine / RedMica 6.1+ ships doorkeeper 5.8 with
/oauth/authorize+/oauth/tokenand an admin UI for OAuth applications, so no authorization server code is needed here — only accepting its tokens and making it discoverable.What changed
authenticate_mcp_user!triesDoorkeeper.authenticate(request)first (setsoauth_scopethe way Redmine core does), then falls back to the existing static API key path. Claude Code / Claude Desktop keep working unchanged.WWW-Authenticate: Bearer resource_metadata="…"so clients can discover OAuth.GET /.well-known/oauth-protected-resource(+/mcpvariant) — RFC 9728GET /.well-known/oauth-authorization-server— RFC 8414POST /mcp/token— exchanging username+password for an API key is obsolete with OAuth, and it was an unthrottled password brute-force surface.No RFC 7591 dynamic client registration: claude.ai's Advanced settings accepts a manually pasted Client ID / Secret.
Two fixes needed to actually connect
scopes_supportedis advertised. Doorkeeper'sdefault_scopesin Redmine is public permissions only, so a client that requests no scope gets denied by every tool.dns_rebinding_protection: falseon the transport. Themcpgem's Host allowlist is loopback-only and returns403 Invalid Host headerfor any real domain — this was hit in testing. Rails' ownconfig.hostsalready performs host authorization.Tests
New
test/integration/oauth_discovery_test.rb— 5 runs, 15 assertions, all passing. Covers both metadata documents, the 401WWW-Authenticateheader, and end-to-endtools/listwith both a doorkeeper access token and a static API key.Setup for reviewers
Redmine admin → Applications → New: redirect URI
https://claude.ai/api/mcp/auth_callback, Confidential checked, Scopes empty. Paste the resulting Client ID / Secret into claude.ai → Connectors → Add custom connector → Advanced settings.🤖 Generated with Claude Code