feat: require OAuth/OIDC bearer auth on HTTP transport (OWASP MCP07)#1209
feat: require OAuth/OIDC bearer auth on HTTP transport (OWASP MCP07)#1209manjunathshiva wants to merge 1 commit into
Conversation
Per OWASP MCP Top 10 (2025) item MCP07 - Insufficient Authentication
& Authorization. The HTTP transport now refuses to serve any request
without a valid OAuth/OIDC bearer token when `oauthIssuer` is
configured, and refuses to start at all when bound to a non-loopback
host without authentication.
This closes the "open HTTP backdoor" gap: previously, running
`mongodb-mcp-server --transport http --httpHost 0.0.0.0` exposed
every configured MongoDB tool to anyone who could reach the port.
Now such deployments must (a) bind only to loopback, or (b)
configure an OIDC issuer whose bearer tokens are validated on every
request. Loopback dev workflows are unaffected.
Scope (intentional v1):
- AuthN only. Per-tool scope mapping (e.g. mcp:read scope required
for read tools) is conceptually separate and tracked as a
follow-up; the middleware exposes the parsed `scope`/`scp` claim
on the auth context so a v2 can add scope checks without
restructuring.
- Bearer-token validation only. RFC 7662 introspection (which would
give revoke-before-expiry) is not implemented; we trust signature
+ exp + iss + aud.
New module: src/transports/auth/
jwksCache.ts
LRUCache-backed wrapper around oauth4webapi's discovery + JWKS
fetch. Caches AuthorizationServer metadata per issuer for a
configurable TTL (default 10 minutes); failures are NOT cached
so transient issuer unavailability self-heals.
oauthMiddleware.ts
Express middleware. Validates `Authorization: Bearer <jwt>` via
oauth4webapi.validateJwtAccessToken (RFC 9068). Verifies
signature against the issuer's JWKS, checks
`iss`/`aud`/`exp`/`nbf`. On failure returns 401 with a
spec-compliant `WWW-Authenticate: Bearer realm=... error=...`
header. On success sets a typed AuthContext
({ sub, scopes, audience, issuer }) on the request, accessible
via the exported getAuthContext() helper. Accepts both RFC 8693
`scope` (space-delimited string) and Azure-style `scp` (array).
The raw token is never stored past the middleware to keep it out
of logs/telemetry.
index.ts
Public surface: JwksCache, createOAuthMiddleware,
getAuthContext, AuthContext, OAuthMiddlewareOptions. Re-exported
from both `mongodb-mcp-server` (lib.ts) and
`mongodb-mcp-server/web` (web.ts) so embedders building custom
HTTP servers can layer auth on top.
New config fields (userConfig.ts):
- oauthIssuer OIDC issuer URL. Validated as a URL at parse time.
- oauthAudience Expected `aud` claim. Required when oauthIssuer
is set; mismatched pairs fail at startup with a
clear error.
- oauthJwksCacheTtlMs How long to cache the issuer's metadata.
Default 600_000 (10 minutes); minimum 1_000.
All three are `overrideBehavior: "not-allowed"` so per-session
overrides cannot disable auth.
Wiring:
- MCPHttpServer.setupMiddlewares mounts createOAuthMiddleware before
any MCP-routing middleware when oauthIssuer + oauthAudience are
set, so unauthenticated requests cannot reach session state.
Mounts the shared JwksCache so all requests share one cache.
- StreamableHttpRunner.validateConfig refuses to start in two cases:
(a) httpHost is non-loopback AND oauthIssuer/oauthAudience are not
set together -> "Refusing to start: ... non-loopback ...
OAuth authentication is not configured"
(b) exactly one of oauthIssuer / oauthAudience is set -> "must be
configured together". Misconfigured pairs are almost always a
mistake; fail loudly.
LogIds (1_006_200 - 1_006_205):
- httpOAuthDisabled / httpOAuthEnabled (startup audit)
- httpOAuthMissingToken (debug; 401)
- httpOAuthInvalidToken (warning; 401 with token error)
- httpOAuthJwksFetchFailure (error)
- httpOAuthDiscoveryFailure (error)
Tests:
- 7 unit tests for createOAuthMiddleware: missing/non-Bearer header
-> 401 invalid_request, issuer unreachable -> 503, oauth4webapi
rejection -> 401 invalid_token, RFC 8693 / Azure / missing scope
claim shapes, audience normalisation.
- 6 unit tests for JwksCache: cache hit, cache miss + fetch, failure
non-caching, error logging paths, clear().
- 4 unit tests for StreamableHttpRunner.validateConfig: refuses on
non-loopback without OAuth, refuses on half-configured OAuth pair
(issuer-only and audience-only), accepts loopback without OAuth.
- tests/unit/common/config.test.ts expectedDefaults includes the
new oauthJwksCacheTtlMs default.
api-extractor reports regenerated.
BREAKING CHANGE: HTTP transport deployments that bind to a
non-loopback interface (httpHost != "127.0.0.1"/"localhost"/"::1")
now require OAuth configuration (oauthIssuer + oauthAudience) and
the server refuses to start without it. Deployments bound to
loopback are unaffected.
nirinchev
left a comment
There was a problem hiding this comment.
We appreciate the PRs you're opening and your desire to improve the security posture of the MongoDB MCP server, but those are significant changes that should be coordinated with the team prior to opening a PR. It is an explicit non-goal for the http transport server to be prescriptive or self-sufficient when it comes to authentication. Instead, the expectation is that users deploy it behind a reverse proxy with auth configured that matches their business needs. We do have a section in the docs that covers this: https://www.mongodb.com/docs/mcp-server/security-best-practices/#remote-mcp-server. While we recognize docs do not solve everything, different customers have different authN/Z needs, which is why we have opted against bundling OAuth into the default MCP server.
I'll bring it up in our team sync as a discussion point and we may decide to change our stance, but until then, we cannot merge this.
|
We want to use Mongo DB MCP Server in Production for Agentic AI Project. We cannot take this as it is not OWSAP MCP10 compliant. We made changes to fix the security issues and also few more like removing delete operations , only allow certificates. All changes are in https://github.com/manjunathshiva/mongodb-mcp-server/tree/owsap_to10_fixes_manjunath branch and it is working fine. Thought of giving back OWSAP fixes to community. Hence raised 3 PR's to fix all OWSAP MCP 10 issues. |
|
Understood and appreciate contributing the fixes - main problem is that since this is a product used in many different environments, we need to ensure the changes will not be disruptive to other customers. For example, someone who has an external system managing AuthN/Z would probably need a way to disable the built-in one. The MongoDB MCP server is intended to be pluggable and extensible so customer who have more specific needs can use it as a library and inject their business/auth logic. So rather than changing the core library, the recommended approach would be to use the extension points to add your validations (this should work for OAuth, but may not work that well for some of the other PRs you've raised, where a different approach may be needed). |
Per OWASP MCP Top 10 (2025) item MCP07 - Insufficient Authentication & Authorization. The HTTP transport now refuses to serve any request without a valid OAuth/OIDC bearer token when
oauthIssueris configured, and refuses to start at all when bound to a non-loopback host without authentication.This closes the "open HTTP backdoor" gap: previously, running
mongodb-mcp-server --transport http --httpHost 0.0.0.0exposed every configured MongoDB tool to anyone who could reach the port. Now such deployments must (a) bind only to loopback, or (b) configure an OIDC issuer whose bearer tokens are validated on every request. Loopback dev workflows are unaffected.Scope (intentional v1):
scope/scpclaim on the auth context so a v2 can add scope checks without restructuring.New module: src/transports/auth/jwksCache.ts
LRUCache-backed wrapper around oauth4webapi's discovery + JWKS fetch. Caches AuthorizationServer metadata per issuer for a configurable TTL (default 10 minutes); failures are NOT cached so transient issuer unavailability self-heals.
oauthMiddleware.ts
Express middleware. Validates
Authorization: Bearer <jwt>via oauth4webapi.validateJwtAccessToken (RFC 9068). Verifies signature against the issuer's JWKS, checksiss/aud/exp/nbf. On failure returns 401 with a spec-compliantWWW-Authenticate: Bearer realm=... error=...header. On success sets a typed AuthContext ({ sub, scopes, audience, issuer }) on the request, accessible via the exported getAuthContext() helper. Accepts both RFC 8693scope(space-delimited string) and Azure-stylescp(array). The raw token is never stored past the middleware to keep it out of logs/telemetry.index.ts
Public surface: JwksCache, createOAuthMiddleware, getAuthContext, AuthContext, OAuthMiddlewareOptions. Re-exported from both
mongodb-mcp-server(lib.ts) andmongodb-mcp-server/web(web.ts) so embedders building custom HTTP servers can layer auth on top.New config fields (userConfig.ts):
audclaim. Required when oauthIssueris set; mismatched pairs fail at startup with a
clear error.
Default 600_000 (10 minutes); minimum 1_000.
All three are
overrideBehavior: "not-allowed"so per-session overrides cannot disable auth.Wiring:
LogIds (1_006_200 - 1_006_205):
Tests:
api-extractor reports regenerated.
BREAKING CHANGE: HTTP transport deployments that bind to a non-loopback interface (httpHost != "127.0.0.1"/"localhost"/"::1") now require OAuth configuration (oauthIssuer + oauthAudience) and the server refuses to start without it. Deployments bound to loopback are unaffected.
Proposed changes
Checklist