Skip to content

Accept OAuth and API-key callers on one HTTP endpoint - #10

Merged
markmnl merged 1 commit into
mainfrom
combined-auth
Oct 9, 2026
Merged

markmnl merged 1 commit into
mainfrom
combined-auth

Conversation

@markmnl

@markmnl markmnl commented Oct 9, 2026

Copy link
Copy Markdown
Owner

Summary

Adds FMSG_MCP_AUTH_MODE=oauth+api-key (HTTP only) so a single public MCP URL can serve both API-key callers and MCP-authorization (OAuth) callers. It requires the same complete FMSG_MCP_OAUTH_* configuration as oauth mode. stdio is unchanged.

Routing is per request, by the Authorization header:

  • Bearer fmsgk_... → the existing API-key path, as in api-key mode (key exchanged at the Web API, never sent to the authorization server; no OAuth scopes).
  • Anything else (another bearer token, or a missing or malformed header) → the existing OAuth path, as in oauth mode, including scope checks and the 401 challenge with resource_metadata.

Design notes:

  • The API-key and OAuth mechanisms keep separate providers, caches and MCP handlers. A caller is only ever resolved by the provider that authenticated it, and lease release goes back to that same provider.
  • The RFC 9728 protected-resource metadata routes are served, and attachment downloads (/mcp/attachments/...) accept either credential.
  • A rejected API key in combined mode returns 401 error="invalid_token" with the same resource_metadata parameter, so hosts can discover sign-in. The internal API-key fmsg scope is left out of that challenge because it isn't an authorization-server scope, and a host that followed it would request an invalid scope.
  • api-key and oauth modes behave as before. In oauth mode an fmsgk_ token still goes to OAuth validation and is rejected there.
  • HttpServerHandle gains providers: { apiKey?, oauth? }. provider stays as the OAuth provider when OAuth is configured, and the API-key provider otherwise.

Tests

New HTTP OAuth combined with API keys suite in test/oauth.test.ts, run against the local authorization server:

  • metadata routes are served; no token, a non-JWT token or a wrong-audience JWT gets an OAuth 401 with resource_metadata, and the API-key verifier is never called
  • an API key works end to end (whoami, send) without any authorization-server request or OAuth verifier call
  • an OAuth token works through token exchange, the API-key verifier is never called, and nothing hits /fmsg/token
  • a rejected API key is challenged with resource_metadata and no scope, and is never sent to the OAuth verifier
  • OAuth scope checks still apply (read-only token → 403 for send_message)
  • attachment downloads work for both kinds of caller, unauthenticated or rejected downloads get the metadata challenge, and a write-only OAuth token is refused
  • configuration: oauth+api-key parsing, stdio refusal, required settings and the invalid-mode message

npm run typecheck, npm run build and npm test pass (157 tests).

🤖 Generated with Claude Code

Add FMSG_MCP_AUTH_MODE=oauth+api-key for HTTP. It requires the full
OAuth configuration and routes each request by its Authorization
header: `Bearer fmsgk_...` uses the existing API-key path unchanged,
and every other request (including unauthenticated ones) uses the
OAuth path with its resource_metadata challenge and scope checks.

The two mechanisms keep separate providers, caches and MCP handlers,
so a caller is only resolved by the provider that authenticated it.
Protected-resource metadata is served and attachment downloads accept
either credential. A rejected API key is also challenged with the
resource_metadata URL (without the internal API-key scope) so hosts
can discover sign-in. api-key and oauth modes behave as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@markmnl
markmnl merged commit 2d70736 into main Oct 9, 2026
4 checks passed
@markmnl
markmnl deleted the combined-auth branch October 9, 2026 07:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant