From 16784d6fd4a2b106305860956354c1ac3481128c Mon Sep 17 00:00:00 2001 From: Chris Coutinho Date: Sun, 14 Jun 2026 11:49:45 +0200 Subject: [PATCH] docs: drop removed token-exchange mode; deprecate superseded auth ADRs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The OAuth token-exchange deployment mode was removed (ADR-022) and has no implementation — only a vestigial `enable_token_exchange` flag remains. Its documentation still presented it as a usable mode, which misleads self-hosters. The only supported deployment modes are single_user_basic, multi_user_basic, and login_flow. Token-exchange removals (how-to/config for a removed mode): - delete docs/CRITICAL-TOKEN-EXCHANGE-PATTERN.md - delete docs/oauth-architecture-comparison.md (orphaned; labelled the removed pass-through mode as "current implementation") - env.sample: drop the "OAUTH TOKEN EXCHANGE MODE" section - docker-compose.yml: drop ENABLE_TOKEN_EXCHANGE/TOKEN_EXCHANGE_CACHE_TTL from the keycloak service (dead flags) - docs/webhook-management-guide.md: drop the token-exchange deployment section - docs/configuration-migration-v2.md: drop the token-exchange migration scenario - docs/observability.md: drop the never-emitted mcp_oauth_token_exchange_total Auth ADR status corrections: - ADR-004: Draft -> Superseded by ADR-022/ADR-023 (token-exchange/federated design not adopted); note the three supported modes. - ADR-002: extend the deprecation pointer to ADR-022/ADR-023. Co-Authored-By: Claude Opus 4.8 (1M context) --- docker-compose.yml | 6 - docs/ADR-002-vector-sync-authentication.md | 2 +- docs/ADR-004-mcp-application-oauth.md | 12 +- docs/CRITICAL-TOKEN-EXCHANGE-PATTERN.md | 348 --------------------- docs/configuration-migration-v2.md | 48 --- docs/oauth-architecture-comparison.md | 323 ------------------- docs/observability.md | 1 - docs/webhook-management-guide.md | 19 +- env.sample | 34 -- 9 files changed, 13 insertions(+), 780 deletions(-) delete mode 100644 docs/CRITICAL-TOKEN-EXCHANGE-PATTERN.md delete mode 100644 docs/oauth-architecture-comparison.md diff --git a/docker-compose.yml b/docker-compose.yml index 762d7b71..5290cfdb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -251,12 +251,6 @@ services: - TOKEN_ENCRYPTION_KEY=${TOKEN_ENCRYPTION_KEY:?TOKEN_ENCRYPTION_KEY must be set in .env (see env.sample)} - TOKEN_STORAGE_DB=/app/data/tokens.db - # ADR-005: Token exchange mode (RFC 8693) - # Exchange MCP tokens (aud: nextcloud-mcp-server) for Nextcloud tokens (aud: http://localhost:8080) - # Provides strict audience separation between MCP session and Nextcloud API access - - ENABLE_TOKEN_EXCHANGE=true - - TOKEN_EXCHANGE_CACHE_TTL=300 # Cache exchanged tokens for 5 minutes (default) - # Login Flow v2 (ADR-022) with external IdP — derived from the # auto-detected LOGIN_FLOW deployment mode; no separate flag needed. - ENABLE_DCR=true diff --git a/docs/ADR-002-vector-sync-authentication.md b/docs/ADR-002-vector-sync-authentication.md index bf2c70ee..95d9c7da 100644 --- a/docs/ADR-002-vector-sync-authentication.md +++ b/docs/ADR-002-vector-sync-authentication.md @@ -6,7 +6,7 @@ ## Status ~~Accepted - Tier 2 (Token Exchange with Delegation) Implemented~~ -**Superseded by ADR-004** - The token exchange implementation exists but doesn't solve the offline access problem. +**Superseded by ADR-004**, and ultimately by [ADR-022](ADR-022-deployment-mode-consolidation.md) (Login Flow v2) + [ADR-023](ADR-023-oauth-as-proxy.md) (OAuth AS proxy). The token-exchange approach was removed; background vector sync now uses Login Flow v2 app passwords. The only supported deployment modes are `single_user_basic`, `multi_user_basic`, and `login_flow`. **Important**: Service account tokens (old Tier 1) have been rejected as they violate OAuth "act on-behalf-of" principles by creating Nextcloud user accounts for the MCP server. diff --git a/docs/ADR-004-mcp-application-oauth.md b/docs/ADR-004-mcp-application-oauth.md index 92e372a5..5af62305 100644 --- a/docs/ADR-004-mcp-application-oauth.md +++ b/docs/ADR-004-mcp-application-oauth.md @@ -1,6 +1,16 @@ # ADR-004: Federated Authentication Architecture for Offline Access -**Status**: Draft +> **⚠️ DEPRECATED / superseded.** The token-exchange and dual-flow "federated +> authentication" design described here was **not adopted**. The MCP server's +> auth was consolidated in [ADR-022](ADR-022-deployment-mode-consolidation.md) +> (Login Flow v2) and [ADR-023](ADR-023-oauth-as-proxy.md) (OAuth AS proxy). +> The only supported deployment modes are **`single_user_basic`**, +> **`multi_user_basic`**, and **`login_flow`** — there is no token-exchange mode. +> The durable ideas from this ADR (MCP server as its own OAuth client; token +> **audience** validation) live on via [ADR-005](ADR-005-token-audience-validation.md) +> and ADR-023. + +**Status**: Superseded by ADR-022 and ADR-023 (token-exchange design not adopted) **Date**: 2025-11-02 **Supersedes**: ADR-002 diff --git a/docs/CRITICAL-TOKEN-EXCHANGE-PATTERN.md b/docs/CRITICAL-TOKEN-EXCHANGE-PATTERN.md deleted file mode 100644 index c3e59d21..00000000 --- a/docs/CRITICAL-TOKEN-EXCHANGE-PATTERN.md +++ /dev/null @@ -1,348 +0,0 @@ -# Token Acquisition Patterns for ADR-004 Progressive Consent - -## Overview - -ADR-004 Progressive Consent establishes the authorization architecture (Flow 1 for client auth, Flow 2 for resource provisioning). This document describes **how tokens are acquired for different operational contexts** within that architecture. - -**Key Principle**: Refresh tokens from Flow 2 (Progressive Consent) should **NEVER** be used for MCP tool calls - they are exclusively for background jobs. - -## Implementation Status - -**Current Status**: ✅ Token exchange infrastructure implemented, available as opt-in feature - -The MCP server supports two token acquisition modes: -1. **Pass-through mode** (default, `ENABLE_TOKEN_EXCHANGE=false`): Simple, stateless -2. **Token exchange mode** (opt-in, `ENABLE_TOKEN_EXCHANGE=true`): Enhanced security with token delegation - -Both modes maintain the critical separation: **refresh tokens are never used for tool calls**. - -## Current Default (Pass-Through Mode) - -### What Happens (ENABLE_TOKEN_EXCHANGE=false): -1. Client gets Flow 1 token (`aud: "mcp-server"`) -2. Client calls MCP tool -3. Server validates Flow 1 token -4. Server passes Flow 1 token to Nextcloud -5. Nextcloud validates token with IdP -6. Refresh tokens (from Flow 2) used **only** for background jobs - -### Characteristics: -- ✅ Simple, stateless operation -- ✅ Clear separation: Flow 1 tokens for sessions, refresh tokens for background -- ✅ Lower latency (no token exchange round-trip) -- ✅ Works with any OAuth IdP - -## Optional Token Exchange Mode - -### Token Exchange Pattern (ENABLE_TOKEN_EXCHANGE=true) - -**MCP Session (Foreground Operations)**: - -``` -┌─────────────┐ Flow 1 Token ┌──────────────┐ -│ MCP Client │ ───(aud: mcp-server)──> │ MCP Server │ -└─────────────┘ └──────────────┘ - │ - Tool Call │ - "search_notes()" │ - ▼ - ┌─────────────────────┐ - │ Token Exchange │ - │ 1. Validate Flow 1 │ - │ 2. Check permission │ - │ 3. Request delegated│ - │ Nextcloud token │ - └─────────────────────┘ - │ - │ Exchange Request - ▼ - ┌─────────────────────┐ - │ IdP Token Endpoint │ - │ (Token Exchange) │ - └─────────────────────┘ - │ - │ Delegated Token - │ (aud: nextcloud) - │ (limited scopes) - │ (short-lived) - ▼ - ┌─────────────────────┐ - │ Nextcloud API Call │ - │ GET /notes │ - └─────────────────────┘ -``` - -**Key Properties of Session Tokens:** -- ✅ Generated **on-demand** during tool execution -- ✅ **Ephemeral** - used only for current operation -- ✅ **NOT stored** - discarded after use -- ✅ **Limited scopes** - only what tool needs (e.g., `notes.read` for search) -- ✅ **Short-lived** - expires quickly (e.g., 5 minutes) - -**Background Jobs (Offline Operations)**: - -``` -┌─────────────────┐ Scheduled Job ┌──────────────┐ -│ Background │ ──────────────────────> │ Worker │ -│ Scheduler │ │ Process │ -└─────────────────┘ └──────────────┘ - │ - │ Use stored - │ refresh token - ▼ - ┌─────────────────────┐ - │ Refresh Token Store │ - │ (Flow 2 provisioned)│ - └─────────────────────┘ - │ - │ Refresh Token - ▼ - ┌─────────────────────┐ - │ IdP Token Endpoint │ - │ (Refresh Grant) │ - └─────────────────────┘ - │ - │ Background Token - │ (aud: nextcloud) - │ (different scopes) - │ (longer-lived) - ▼ - ┌─────────────────────┐ - │ Nextcloud API │ - │ (Background Sync) │ - └─────────────────────┘ -``` - -**Key Properties of Background Tokens:** -- ✅ Obtained from **stored refresh token** (Flow 2) -- ✅ **Different scopes** than session tokens (e.g., `notes:sync`, `files:sync`) -- ✅ **Longer-lived** for background operations -- ✅ **Never used for MCP sessions** -- ✅ **Only for offline/background jobs** - -## Implementation Requirements - -### 1. Token Exchange Endpoint - -Implement RFC 8693 Token Exchange: - -```python -# nextcloud_mcp_server/auth/token_exchange.py - -async def exchange_token_for_delegation( - flow1_token: str, - requested_audience: str = "nextcloud", - requested_scopes: list[str] | None = None -) -> tuple[str, int]: - """ - Exchange Flow 1 MCP token for delegated Nextcloud token. - - This implements RFC 8693 Token Exchange for on-behalf-of delegation. - - IMPORTANT: Nextcloud doesn't support OAuth scopes natively. Scopes are - soft-scopes enforced by the MCP server via @require_scopes decorator, - not by the IdP or Nextcloud. Therefore, requested_scopes are not passed - to the IdP during token exchange. - - Args: - flow1_token: The MCP session token (aud: "mcp-server") - requested_audience: Target audience (usually "nextcloud") - requested_scopes: Ignored (Nextcloud doesn't support scopes) - - Returns: - Tuple of (delegated_token, expires_in) - """ - # 1. Validate Flow 1 token (audience check) - # 2. Check user has provisioned Nextcloud access (Flow 2) - # 3. Request token exchange from IdP (without scopes - Nextcloud doesn't support them) - # 4. Return ephemeral delegated token -``` - -### 2. Unified get_client() Pattern - -The token acquisition mode is handled transparently by `get_client()`: - -```python -# nextcloud_mcp_server/context.py - -async def get_client(ctx: Context) -> NextcloudClient: - """ - Get the appropriate Nextcloud client based on authentication mode. - - This function handles three modes: - 1. BasicAuth mode: Returns shared client from lifespan context - 2. OAuth pass-through mode (ENABLE_TOKEN_EXCHANGE=false, default): - Verifies Flow 1 token and passes it to Nextcloud - 3. OAuth token exchange mode (ENABLE_TOKEN_EXCHANGE=true): - Exchanges Flow 1 token for ephemeral Nextcloud token via RFC 8693 - """ - settings = get_settings() - lifespan_ctx = ctx.request_context.lifespan_context - - # BasicAuth mode - use shared client (no token exchange) - if hasattr(lifespan_ctx, "client"): - return lifespan_ctx.client - - # OAuth mode (has 'nextcloud_host' attribute) - if hasattr(lifespan_ctx, "nextcloud_host"): - # Check if token exchange is enabled - if settings.enable_token_exchange: - # Token exchange mode: Exchange Flow 1 token for ephemeral Nextcloud token - return await get_session_client_from_context( - ctx, lifespan_ctx.nextcloud_host - ) - else: - # Pass-through mode (default): Verify and pass Flow 1 token to Nextcloud - return get_client_from_context(ctx, lifespan_ctx.nextcloud_host) -``` - -### 3. MCP Tool Pattern (No Changes Required!) - -Tools use the same pattern regardless of token acquisition mode: - -```python -@mcp.tool() -@require_scopes("notes.read") # Soft-scope enforced by MCP server, not Nextcloud -@require_provisioning -async def nc_notes_search_notes(query: str, ctx: Context) -> SearchNotesResponse: - """Search notes by title or content.""" - - # get_client() handles both pass-through and token exchange modes - client = await get_client(ctx) - - # Execute operation - results = await client.notes.search_notes(query=query) - - # In token exchange mode, ephemeral token is automatically discarded - # In pass-through mode, Flow 1 token was validated and passed through - return SearchNotesResponse(results=results) -``` - -**Key Benefit**: Tools don't need to know which mode is active. The token acquisition pattern is configured at the server level via `ENABLE_TOKEN_EXCHANGE`. - -### 4. Background Job Pattern - -Background jobs use a **different token acquisition pattern** - they use refresh tokens from Flow 2: - -```python -# Background worker -async def sync_notes_job(user_id: str): - """Background job to sync notes.""" - - # Get refresh token stored during Flow 2 (Progressive Consent) - token_storage = get_token_storage() - refresh_token = await token_storage.get_refresh_token(user_id) - - if not refresh_token: - logger.warning(f"No refresh token for user {user_id}") - return - - # Use refresh token to get Nextcloud access token - idp_client = get_idp_client() - response = await idp_client.refresh_token( - refresh_token=refresh_token, - audience='nextcloud' - ) - - # Create client with background token (can be cached) - client = NextcloudClient.from_token( - base_url=NEXTCLOUD_HOST, - token=response.access_token, - username=user_id - ) - - # Perform background sync - await client.notes.sync_all() -``` - -**Key differences from tool calls:** -- Uses refresh tokens from Flow 2 (Progressive Consent provisioning) -- Tokens can be cached for efficiency (longer-lived operations) -- No user interaction possible (offline) -- Never triggered during MCP tool execution - -## Security Benefits - -### Proper Token Exchange: -1. ✅ **Least Privilege**: Each operation gets only needed scopes -2. ✅ **Time-Limited**: Session tokens expire quickly -3. ✅ **Audit Trail**: Each exchange can be logged -4. ✅ **Token Isolation**: Session ≠ Background tokens -5. ✅ **Revocation**: Can revoke background access without affecting active sessions - -### Current Incorrect Pattern: -1. ❌ **Over-Privileged**: Refresh token has all scopes -2. ❌ **Long-Lived**: Same token reused indefinitely -3. ❌ **No Separation**: Sessions and background jobs use same credential -4. ❌ **Revocation Issues**: Revoking affects everything - -## Implementation Steps - -### Phase 1: Token Exchange (High Priority) -1. Implement RFC 8693 token exchange endpoint -2. Update Token Broker with `get_session_token()` vs `get_background_token()` -3. Modify tool pattern to use token exchange - -### Phase 2: Scope Separation (High Priority) -1. Define session scopes vs background scopes -2. Update provisioning flow to request appropriate scopes -3. Validate scopes in token exchange - -### Phase 3: Background Jobs (Medium Priority) -1. Implement background worker pattern -2. Create scheduled jobs (note sync, etc.) -3. Use background token pattern - -### Phase 4: Testing (High Priority) -1. Test token exchange flow end-to-end -2. Verify session tokens are ephemeral -3. Verify background tokens are separate -4. Load test token exchange performance - -## References - -- **RFC 8693**: OAuth 2.0 Token Exchange -- **RFC 9068**: JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens -- **ADR-004**: Progressive Consent OAuth Flows -- **OAuth 2.0 Delegation**: On-Behalf-Of vs Impersonation patterns - -## Status - -**Current Status**: ✅ Token exchange infrastructure implemented, available as opt-in feature -**Modes Available**: -- ✅ Pass-through mode (default, `ENABLE_TOKEN_EXCHANGE=false`): Simple, stateless -- ✅ Token exchange mode (opt-in, `ENABLE_TOKEN_EXCHANGE=true`): Enhanced security - -**Implementation Complete**: -- ✅ `token_exchange.py` module with RFC 8693 support -- ✅ Fallback to refresh grant when RFC 8693 not supported -- ✅ `get_client()` unified pattern (handles both modes transparently) -- ✅ Tokens never cached in token exchange mode (ephemeral) -- ✅ Background jobs use separate pattern (refresh tokens from Flow 2) - -## Configuration - -To enable token exchange mode: - -```bash -# docker-compose.yml or .env -ENABLE_TOKEN_EXCHANGE=true -``` - -When enabled, all MCP tool calls will use token exchange (RFC 8693) to obtain ephemeral Nextcloud tokens. When disabled (default), Flow 1 tokens are passed through to Nextcloud. - -## Nextcloud Scope Limitation - -**IMPORTANT**: Nextcloud does not support OAuth scopes natively. Scopes like "notes.read" are **soft-scopes** enforced by the MCP server via `@require_scopes` decorator, not by the IdP or Nextcloud. - -This means: -- Token exchange provides audit and delegation benefits, not scope restriction -- All Nextcloud tokens have equivalent permissions at the Nextcloud level -- Fine-grained access control is enforced by MCP server, not Nextcloud - -## Next Actions (Optional Enhancements) - -1. [ ] Add integration tests for token exchange mode with actual MCP tools -2. [ ] Document background job patterns for scheduled sync operations -3. [ ] Add metrics for token exchange performance -4. [ ] Consider making token exchange the default in future major version diff --git a/docs/configuration-migration-v2.md b/docs/configuration-migration-v2.md index b20f8cb3..d7e4964c 100644 --- a/docs/configuration-migration-v2.md +++ b/docs/configuration-migration-v2.md @@ -233,54 +233,6 @@ NEXTCLOUD_OIDC_CLIENT_SECRET=secret --- -### Scenario 5: Token Exchange Mode with Semantic Search - -**Before (v0.57.x):** -```bash -NEXTCLOUD_HOST=https://nextcloud.example.com -ENABLE_TOKEN_EXCHANGE=true - -# Both required -ENABLE_OFFLINE_ACCESS=true -VECTOR_SYNC_ENABLED=true - -TOKEN_ENCRYPTION_KEY=your-key-here -TOKEN_STORAGE_DB=/app/data/tokens.db -TOKEN_EXCHANGE_CACHE_TTL=300 -QDRANT_URL=http://qdrant:6333 -OLLAMA_BASE_URL=http://ollama:11434 -``` - -**After (v0.58.0+ - Simplified):** -```bash -NEXTCLOUD_HOST=https://nextcloud.example.com -ENABLE_TOKEN_EXCHANGE=true - -# Optional: Explicit mode declaration -MCP_DEPLOYMENT_MODE=oauth_token_exchange - -# One variable! -ENABLE_SEMANTIC_SEARCH=true # Auto-enables background operations - -TOKEN_ENCRYPTION_KEY=your-key-here -TOKEN_STORAGE_DB=/app/data/tokens.db -TOKEN_EXCHANGE_CACHE_TTL=300 -QDRANT_URL=http://qdrant:6333 -OLLAMA_BASE_URL=http://ollama:11434 -``` - -**What Changed:** -- ✅ Semantic search auto-enables background operations -- ✅ Explicit mode declaration available - -**Migration Steps:** -1. Replace `VECTOR_SYNC_ENABLED=true` with `ENABLE_SEMANTIC_SEARCH=true` -2. Remove `ENABLE_OFFLINE_ACCESS=true` (auto-enabled) -3. Optionally add `MCP_DEPLOYMENT_MODE=oauth_token_exchange` -4. Restart server - ---- - ## Understanding Automatic Dependency Resolution ### How It Works diff --git a/docs/oauth-architecture-comparison.md b/docs/oauth-architecture-comparison.md deleted file mode 100644 index 65db9eb6..00000000 --- a/docs/oauth-architecture-comparison.md +++ /dev/null @@ -1,323 +0,0 @@ -# OAuth Architecture Comparison: MCP Server Authentication Patterns - -This document compares three authentication architectures for the MCP server, explaining the evolution from pass-through authentication to true offline access capabilities. - -## Pattern 1: Pass-Through Authentication (Current Implementation) - -### Architecture -``` -┌─────────────┐ OAuth Flow ┌─────────────┐ -│ MCP Client │◄──────────────────│ OAuth │ -│ (Claude) │ │ Provider │ -└──────┬──────┘ └─────────────┘ - │ - │ Access Token - │ (per request) - ▼ -┌─────────────┐ ┌─────────────┐ -│ MCP Server │───────────────────►│ Nextcloud │ -│(Pass-through) │ APIs │ -└─────────────┘ └─────────────┘ -``` - -### Characteristics -| Aspect | Description | -|--------|-------------| -| **Token Flow** | MCP Client → MCP Server → Nextcloud | -| **Token Storage** | None (tokens exist only during request) | -| **Offline Access** | ❌ Impossible | -| **Background Workers** | ❌ Not supported | -| **User Consent** | Single OAuth flow (client-managed) | -| **Complexity** | Low | -| **Security** | High (no token persistence) | - -### How It Works -1. MCP Client performs OAuth with provider -2. Client includes access token in each MCP request -3. MCP Server validates token and forwards to Nextcloud -4. Token discarded after request completes - -### Limitations -- No operations possible without active MCP session -- Background sync/indexing impossible -- Cannot refresh tokens independently - ---- - -## Pattern 2: Token Exchange Delegation (ADR-002 - Flawed) - -### Architecture -``` -┌─────────────┐ ┌─────────────┐ -│ MCP Client │────────────────────│ OAuth │ -│ (Claude) │ │ Provider │ -└──────┬──────┘ └──────┬──────┘ - │ │ - │ Access Token │ Service Account Token - ▼ ▼ -┌─────────────────────────────────────────────┐ -│ MCP Server │ -│ ┌────────────────────────────────────┐ │ -│ │ Token Exchange (RFC 8693) │ │ -│ │ Subject: Service Account │ │ -│ │ Target: User │ │ -│ └────────────────────────────────────┘ │ -└───────────────┬─────────────────────────────┘ - │ Exchanged Token - ▼ - ┌─────────────┐ - │ Nextcloud │ - │ APIs │ - └─────────────┘ -``` - -### Characteristics -| Aspect | Description | -|--------|-------------| -| **Token Flow** | Service Account → Exchange → User Token | -| **Token Storage** | None (MCP server still stateless) | -| **Offline Access** | ❌ Still impossible (circular dependency) | -| **Background Workers** | ❌ Requires service account (rejected) | -| **User Consent** | Implicit through service account | -| **Complexity** | High | -| **Security** | ⚠️ Service accounts violate OAuth principles | - -### Why It Fails -1. **Circular Dependency**: To exchange tokens, you need a token to exchange -2. **Service Account Problem**: Creates Nextcloud user identity for service -3. **OAuth Violation**: Service acts as itself, not on behalf of users -4. **No Bootstrap**: Still can't obtain initial tokens offline - -### The Fatal Flaw -``` -Q: How does background worker get tokens? -A: Use token exchange with service account - -Q: How does service account get authorized? -A: Client credentials grant creates user account (violates OAuth) - -Q: Can we use user's refresh token? -A: MCP server never sees refresh tokens (by design) -``` - ---- - -## Pattern 3: Sign-in with Nextcloud (Previous ADR-004 Draft) - -### Architecture -``` -┌─────────────┐ ┌─────────────────┐ ┌────────────┐ -│ MCP Client ├───────────────────> │ MCP Server ├────────────────────>│ Nextcloud │ -│ (Claude) │ (MCP Protocol) │ (OAuth Client) │ (OIDC + APIs) │ (IdP) │ -└─────────────┘ └─────────────────┘ └────────────┘ - │ - ┌──────▼────────┐ - │ Token Storage │ - │ (NC Tokens) │ - └───────────────┘ -``` - -### Characteristics -| Aspect | Description | -|--------|-------------| -| **Token Flow** | MCP Server uses Nextcloud as identity provider | -| **Token Storage** | ✅ Encrypted Nextcloud refresh tokens | -| **Offline Access** | ✅ Full support | -| **Background Workers** | ✅ Use stored refresh tokens | -| **User Consent** | Single OAuth flow (Nextcloud only) | -| **Complexity** | Medium | -| **Security** | High (with token rotation) | - -### How It Works -1. **Initial Setup**: - - User tries to use MCP tool - - MCP server returns auth required - - User authenticates with Nextcloud's OIDC endpoint - - Nextcloud may use user_oidc to delegate to external IdP (Keycloak, etc.) - - MCP server stores Nextcloud-issued refresh token (encrypted) - -2. **Subsequent Requests**: - - MCP server uses stored Nextcloud tokens - - Refreshes automatically when expired - - No client involvement needed - -3. **Background Operations**: - - Worker retrieves stored refresh token - - Refreshes with Nextcloud directly - - Performs operations independently - -### Advantages -- ✅ Single sign-on with Nextcloud -- ✅ True offline access capability -- ✅ OAuth-compliant with proper consent -- ✅ Supports external IdPs via user_oidc -- ✅ Simpler integration - only one OAuth endpoint - -### Trade-offs -- Authentication flows through Nextcloud -- Nextcloud manages IdP relationships (via user_oidc) -- MCP server only knows about Nextcloud, not the underlying IdP - ---- - -## Pattern 4: Federated Authentication Architecture (ADR-004 - Solution) - -### Architecture -``` -┌─────────────┐ ┌─────────────────┐ ┌──────────────┐ ┌────────────┐ -│ MCP Client │◄──────401──────│ MCP Server │◄────OAuth──────│ Shared IdP │──Validates──►│ Nextcloud │ -│ (Claude) │ │ (OAuth Client) │ (On-Behalf) │ (Keycloak) │ Tokens │(Resource) │ -└─────────────┘ └─────────────────┘ └──────────────┘ └────────────┘ - │ - ┌───────▼────────┐ - │ Token Storage │ - │ (IdP Tokens) │ - └────────────────┘ -``` - -### Characteristics -| Aspect | Description | -|--------|-------------| -| **Token Flow** | Shared IdP issues tokens for Nextcloud access | -| **Token Storage** | ✅ Encrypted IdP refresh tokens | -| **Offline Access** | ✅ Full support | -| **Background Workers** | ✅ Use stored IdP refresh tokens | -| **User Consent** | Single OAuth flow (IdP manages consent) | -| **Complexity** | Medium-High | -| **Security** | Highest (enterprise-grade IdP) | - -### How It Works -1. **Initial Setup**: - - MCP client connects, receives 401 - - Browser opens MCP server OAuth URL - - MCP server redirects to shared IdP - - User authenticates once to IdP - - IdP shows consent for both identity and Nextcloud access - - MCP server stores IdP refresh token (encrypted) - - MCP server issues session token to client - -2. **Subsequent Requests**: - - MCP server validates session token - - Uses stored IdP token for Nextcloud - - Refreshes with IdP when expired - - No client involvement needed - -3. **Background Operations**: - - Worker retrieves stored IdP refresh token - - Gets new access token from IdP - - Uses token to access Nextcloud - - Performs operations independently - -### Advantages -- ✅ True single sign-on (SSO) -- ✅ Enterprise-ready with SAML/LDAP support -- ✅ OAuth-compliant with proper delegation -- ✅ Direct IdP relationship - no intermediary -- ✅ Flexible - can swap resource servers -- ✅ Industry-standard federated pattern - -### Trade-offs -- Requires shared IdP infrastructure -- More complex initial setup -- Token validation overhead - ---- - -## Comparison Matrix - -| Feature | Pass-Through | Token Exchange | Sign-in with NC | Federated Auth | -|---------|--------------|----------------|-----------------|----------------| -| **Offline Access** | ❌ No | ❌ No | ✅ Yes | ✅ Yes | -| **Background Workers** | ❌ No | ❌ No* | ✅ Yes | ✅ Yes | -| **Token Storage** | None | None | NC refresh tokens | IdP refresh tokens | -| **OAuth Compliance** | ✅ Full | ⚠️ Violates | ✅ Full | ✅ Full | -| **User Consent** | Once | Implicit | Once (NC) | Once (IdP) | -| **Implementation Complexity** | Low | High | Medium | Medium-High | -| **Security** | High | Medium | High | Highest | -| **Enterprise Ready** | ❌ No | ❌ No | ⚠️ Indirect | ✅ Yes | -| **Identity Provider** | Client-managed | N/A | Nextcloud (+user_oidc) | Shared IdP | -| **Suitable For** | Interactive only | N/A (flawed) | Small teams | Enterprise | - -\* *Requires service accounts that violate OAuth principles* - ---- - -## Evolution Summary - -### Stage 1: Simple Pass-Through ✅ -- **Goal**: Basic MCP functionality -- **Result**: Works well for interactive use -- **Limitation**: No offline capabilities - -### Stage 2: Attempted Delegation ❌ -- **Goal**: Enable offline access without changing architecture -- **Result**: Circular dependencies, OAuth violations -- **Learning**: MCP protocol constraints are fundamental - -### Stage 3: Sign-in with Nextcloud ⚠️ -- **Goal**: True offline access with OAuth compliance -- **Result**: MCP server uses Nextcloud as identity provider -- **Limitation**: Tight coupling to Nextcloud, no enterprise IdP - -### Stage 4: Federated Pattern ✅ -- **Goal**: Enterprise-ready offline access -- **Result**: Shared IdP for both MCP server and Nextcloud -- **Trade-off**: Additional infrastructure justified by enterprise needs - ---- - -## Key Insights - -1. **Pattern 3 vs Pattern 4**: Both support external IdPs, but differ in integration approach: - - Pattern 3: MCP → Nextcloud OIDC → (user_oidc) → External IdP - - Pattern 4: MCP → External IdP directly (Nextcloud also uses same IdP) - - Choose Pattern 3 for Nextcloud-centric deployments, Pattern 4 for IdP-centric enterprises - -2. **The MCP Protocol Boundary**: The MCP protocol creates a fundamental boundary between client and server token management. Attempting to breach this boundary (ADR-002) leads to architectural contradictions. - -3. **Service Accounts Don't Solve User Problems**: Using service accounts for user operations violates OAuth's core principle of acting on behalf of users, not as a service identity. - -4. **Double OAuth is Industry Standard**: Major platforms (Zapier, IFTTT, Microsoft Power Automate) use this pattern - the integration platform is an OAuth client that maintains its own relationships with upstream services. - -5. **Refresh Tokens Are The Solution**: The OAuth spec designed refresh tokens specifically for offline access. Rejecting them (as ADR-002 did) means rejecting the standard solution. - -6. **Complexity is Justified**: The additional complexity of managing OAuth flows is acceptable when offline access is a requirement. The alternative is no offline access at all. - ---- - -## Recommendations - -### For Simple Deployments -Use **Pattern 1 (Pass-Through)** if: -- Offline access not needed -- Only interactive operations required -- Simplicity is priority - -### For Teams Using Nextcloud -Use **Pattern 3 (Sign-in with Nextcloud)** if: -- Background sync/indexing required -- Nextcloud manages your authentication -- Can use external IdPs via user_oidc -- Prefer single integration point through Nextcloud - -### For Enterprise Deployments -Use **Pattern 4 (Federated Authentication)** if: -- Enterprise IdP already exists (Keycloak, Okta, Azure AD) -- Multiple resource servers beyond Nextcloud -- Compliance requirements for centralized auth -- Building platform for multiple organizations - -### Never Use Pattern 2 -Token Exchange with service accounts should not be used as it: -- Doesn't enable true offline access -- Violates OAuth principles -- Adds complexity without solving the problem - ---- - -## References - -- [ADR-002: Vector Database Background Sync Authentication (Deprecated)](./ADR-002-vector-sync-authentication.md) -- [ADR-004: MCP Server as OAuth Client for Offline Access](./ADR-004-mcp-application-oauth.md) -- [RFC 6749: OAuth 2.0 Framework](https://datatracker.ietf.org/doc/html/rfc6749) -- [RFC 8693: OAuth 2.0 Token Exchange](https://datatracker.ietf.org/doc/html/rfc8693) \ No newline at end of file diff --git a/docs/observability.md b/docs/observability.md index 03b18453..73528963 100644 --- a/docs/observability.md +++ b/docs/observability.md @@ -72,7 +72,6 @@ The Helm chart has moved to a [separate repository](https://github.com/cbcoutinh ### OAuth Flow Metrics - `mcp_oauth_token_validations_total` - Token validation count -- `mcp_oauth_token_exchange_total` - Token exchange operations - `mcp_oauth_token_cache_hits_total` - Cache hit/miss rate - `mcp_oauth_refresh_token_operations_total` - Refresh token storage ops diff --git a/docs/webhook-management-guide.md b/docs/webhook-management-guide.md index c0e55003..cdc2cb9e 100644 --- a/docs/webhook-management-guide.md +++ b/docs/webhook-management-guide.md @@ -207,24 +207,7 @@ php occ webhook_listeners:remove --- -### 4. OAuth Token Exchange (RFC 8693) - -**Configuration:** -```bash -NEXTCLOUD_HOST=http://nextcloud.example.com -ENABLE_TOKEN_EXCHANGE=true -ENABLE_BACKGROUND_OPERATIONS=true -TOKEN_ENCRYPTION_KEY= -TOKEN_STORAGE_DB=/app/data/tokens.db -VECTOR_SYNC_ENABLED=true -``` - -**Enable/Disable Webhooks:** -Same process as OAuth Single-Audience. The token exchange happens transparently when the MCP server accesses Nextcloud APIs. - ---- - -### 5. Smithery Stateless +### 4. Smithery Stateless **Configuration:** - Configuration from session URL params diff --git a/env.sample b/env.sample index e845e94d..906ab3f2 100644 --- a/env.sample +++ b/env.sample @@ -101,40 +101,6 @@ NEXTCLOUD_PASSWORD= # Optional features (semantic search, document processing): # See "Optional Features" section below -# ============================================ -# OAUTH TOKEN EXCHANGE MODE (Advanced) -# ============================================ -# Multi-user OAuth with RFC 8693 token exchange -# Use for: Advanced deployments requiring separate MCP and Nextcloud tokens -# MCP tokens are separate from Nextcloud tokens -# -# Required: -#ENABLE_TOKEN_EXCHANGE=true -# -# Optional - Pre-registered OAuth Client: -# If you pre-register the client instead of using DCR: -#NEXTCLOUD_OIDC_CLIENT_ID= -#NEXTCLOUD_OIDC_CLIENT_SECRET= -# -# Optional - Token Exchange Configuration: -# Cache TTL in seconds (default: 300 = 5 minutes) -#TOKEN_EXCHANGE_CACHE_TTL=300 -# -# Optional - Background Operations: -# Note: ENABLE_SEMANTIC_SEARCH automatically enables this in multi-user modes -#ENABLE_BACKGROUND_OPERATIONS=true -#TOKEN_ENCRYPTION_KEY= -#TOKEN_STORAGE_DB=/app/data/tokens.db -# -# Optional - Custom OIDC Discovery: -#NEXTCLOUD_OIDC_DISCOVERY_URL= -# -# MCP Server URL (for OAuth redirects): -#NEXTCLOUD_MCP_SERVER_URL=http://localhost:8000 -# -# Optional features (semantic search, document processing): -# See "Optional Features" section below - # ============================================ # OAUTH CLIENT ALLOWLISTS (OAuth modes) # ============================================