fix: restructure routes to prevent SessionAuthBackend from interfering with FastMCP OAuth
SessionAuthBackend middleware was wrapping the entire app including FastMCP, which prevented FastMCP's OAuth token verification from running properly. When SessionAuthBackend returned None for /mcp paths, Starlette marked requests as "anonymous" and allowed them through, bypassing FastMCP's authentication. Changes: 1. Route restructuring (app.py): - Create separate Starlette app for browser routes (/user, /user/page) - Apply SessionAuthBackend only to browser app - Mount browser app at /user/* before FastMCP - Mount FastMCP at / (catch-all with its own OAuth) - Remove global SessionAuthBackend middleware 2. SessionAuthBackend cleanup (session_backend.py): - Remove path exclusion logic (no longer needed) - Simplify to only handle browser routes - Update docstring to reflect mount-based isolation Benefits: - FastMCP's OAuth token verification now runs properly - No middleware interference between authentication mechanisms - Clear separation: SessionAuth for browser UI, OAuth Bearer for MCP clients - Tests confirm OAuth authentication works correctly Testing: - All OAuth tests pass (test_mcp_oauth_*, test_jwt_*) - Browser routes still require session auth - FastMCP routes use OAuth Bearer tokens exclusively 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -37,9 +37,9 @@ class SessionAuthBackend(AuthenticationBackend):
|
||||
) -> tuple[AuthCredentials, SimpleUser] | None:
|
||||
"""Authenticate the request based on session cookie or BasicAuth mode.
|
||||
|
||||
For paths that use other authentication mechanisms (OAuth Bearer tokens,
|
||||
public endpoints), this backend returns None to skip session authentication
|
||||
and allow those mechanisms to handle the request.
|
||||
This backend is only applied to browser routes (/user/*) via a separate
|
||||
Starlette app mount. FastMCP routes use their own OAuth Bearer token
|
||||
authentication.
|
||||
|
||||
Args:
|
||||
conn: HTTP connection
|
||||
@@ -47,23 +47,6 @@ class SessionAuthBackend(AuthenticationBackend):
|
||||
Returns:
|
||||
Tuple of (credentials, user) if authenticated, None otherwise
|
||||
"""
|
||||
# Skip session auth for paths that use other authentication methods
|
||||
# or are publicly accessible
|
||||
excluded_paths = [
|
||||
"/mcp", # FastMCP OAuth Bearer tokens (handled by FastMCP's auth provider)
|
||||
"/.well-known/oauth-protected-resource", # Public PRM metadata
|
||||
"/health/live", # Health checks (public)
|
||||
"/health/ready",
|
||||
"/oauth/login", # Login flow (no auth required to access login page)
|
||||
"/oauth/login-callback", # OAuth callback (receives code from IdP)
|
||||
"/oauth/authorize", # Flow 1 authorize endpoint (no session required)
|
||||
]
|
||||
|
||||
if any(conn.url.path.startswith(path) for path in excluded_paths):
|
||||
# Don't interfere - let other auth mechanisms handle these paths
|
||||
logger.debug(f"Skipping session auth for excluded path: {conn.url.path}")
|
||||
return None
|
||||
|
||||
# BasicAuth mode: Always authenticated as the configured user
|
||||
if not self.oauth_enabled:
|
||||
username = os.getenv("NEXTCLOUD_USERNAME", "admin")
|
||||
|
||||
Reference in New Issue
Block a user