Smithery is no longer a supported deployment mode. Remove all Smithery-specific code paths, middleware, configuration, and tests. This simplifies the codebase by eliminating DeploymentMode enum, SmitheryConfigMiddleware, session config context variables, and the smithery_main entrypoint. Files deleted: Dockerfile.smithery, smithery.yaml, smithery_main.py ADR-016 retained with deprecated status for historical reference. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
17 KiB
Authentication Flows by Deployment Mode
This document provides a unified reference for authentication flows across all deployment modes. For configuration details, see Authentication. For OAuth protocol details, see OAuth Architecture.
Quick Reference Matrix
| Mode | Client → MCP → NC | Background Sync | Astrolabe → MCP |
|---|---|---|---|
| Single-User BasicAuth | Embedded credentials | Same credentials | N/A |
| Multi-User BasicAuth | Header pass-through | App password (optional) | Bearer token |
| OAuth Single-Audience | Multi-audience token | Refresh token exchange | Bearer token |
| OAuth Token Exchange | RFC 8693 exchange | Refresh token exchange | Bearer token |
Communication Patterns
This document covers three distinct communication patterns:
- MCP Client → MCP Server → Nextcloud: Interactive tool calls initiated by users through MCP clients (Claude Desktop, etc.)
- MCP Server → Nextcloud: Background operations like vector sync that run without user interaction
- Astrolabe → MCP Server: Nextcloud app backend communication for settings UI and unified search
Deployment Modes
1. Single-User BasicAuth
Use Case: Personal Nextcloud instance, local development, single-user deployments.
MCP Client → MCP Server → Nextcloud
MCP Client MCP Server Nextcloud
│ │ │
│── MCP Request ─────────────▶│ │
│ (no auth required) │ │
│ │── HTTP + BasicAuth ───────▶│
│ │ Authorization: Basic │
│ │ (embedded credentials) │
│ │◀── API Response ───────────│
│◀── Tool Result ─────────────│ │
Key characteristics:
- Credentials embedded in server configuration (
NEXTCLOUD_USERNAME,NEXTCLOUD_PASSWORD) - Single shared
NextcloudClientcreated at startup - No MCP-level authentication required (server trusts local clients)
- All requests use the same Nextcloud user
Implementation: context.py:78-79 - Returns shared client from lifespan context
Background Sync
Uses the same embedded credentials as interactive requests. The background job accesses Nextcloud with the configured username/password.
Implementation: Background jobs use get_settings() to access credentials
Astrolabe Integration
Not applicable - Astrolabe is only used in multi-user deployments where users need personal settings and token management.
2. Multi-User BasicAuth
Use Case: Internal deployment where users provide their own credentials via HTTP headers.
MCP Client → MCP Server → Nextcloud
MCP Client MCP Server Nextcloud
│ │ │
│── MCP Request ─────────────▶│ │
│ Authorization: Basic │ │
│ (user credentials) │ │
│ │── BasicAuthMiddleware ────▶│
│ │ Extracts credentials │
│ │ │
│ │── HTTP + BasicAuth ───────▶│
│ │ (pass-through) │
│ │◀── API Response ───────────│
│◀── Tool Result ─────────────│ │
Key characteristics:
BasicAuthMiddlewareextracts credentials fromAuthorization: Basicheader- Credentials passed through to Nextcloud (not stored)
- Client created per-request from extracted credentials
- Stateless - no credential storage between requests
Implementation: context.py:187-248 - _get_client_from_basic_auth() extracts credentials from request state
Background Sync (Optional)
Requires ENABLE_OFFLINE_ACCESS=true. Users can store app passwords via Astrolabe for background operations.
Astrolabe MCP Server Nextcloud
│ │ │
│── Store App Password ──────▶│ │
│ (via management API) │ │
│ │── Store in SQLite ────────▶│
│ │ (encrypted) │
│◀── Confirmation ────────────│ │
│ │ │
│ [Background Job] │ │
│ │── Retrieve app password ──▶│
│ │ (from encrypted storage) │
│ │── HTTP + BasicAuth ───────▶│
│ │ (stored app password) │
│ │◀── API Response ───────────│
Requirements:
ENABLE_OFFLINE_ACCESS=trueTOKEN_ENCRYPTION_KEYfor credential encryptionTOKEN_STORAGE_DBfor SQLite storage path
Astrolabe → MCP Server
Astrolabe MCP Server Nextcloud OIDC
│ │ │
│── OAuth Flow ──────────────▶│◀── Token from IdP ────────▶│
│ (user initiates) │ │
│ │ │
│── Bearer Token ────────────▶│ │
│ (management API calls) │ │
│ │── Validate via JWKS ──────▶│
│ │ (or introspection) │
│◀── API Response ────────────│ │
Key characteristics:
- Astrolabe has its own OAuth client (
astrolabe_client_idin Nextcloud config) - Tokens are validated by MCP server using Nextcloud OIDC JWKS
- Authorization check:
token.sub == requested_resource_owner - Any valid Nextcloud OIDC token accepted (relaxed audience validation per ADR-018)
Implementation: unified_verifier.py:120-183 - verify_token_for_management_api() validates without strict audience check
3. OAuth Single-Audience (Default)
Use Case: Multi-user deployment with OAuth authentication. Tokens work for both MCP and Nextcloud.
This is the default mode when NEXTCLOUD_USERNAME/NEXTCLOUD_PASSWORD are not set.
MCP Client → MCP Server → Nextcloud
MCP Client MCP Server Nextcloud
│ │ │
│── Bearer Token ────────────▶│ │
│ aud: ["mcp-server", │ │
│ "nextcloud"] │ │
│ │── Validate MCP audience ──▶│
│ │ (UnifiedTokenVerifier) │
│ │ │
│ │── HTTP + Same Token ──────▶│
│ │ Authorization: Bearer │
│ │ (multi-audience token) │
│ │ │
│ │ NC validates its own aud │
│ │◀── API Response ───────────│
│◀── Tool Result ─────────────│ │
Key characteristics:
- Token contains both audiences:
aud: ["mcp-server", "nextcloud"] - MCP server validates only MCP audience (per RFC 7519)
- Nextcloud independently validates its own audience
- No token exchange needed - same token used throughout
- Stateless operation for interactive requests
Token validation flow:
UnifiedTokenVerifier.verify_token()validates MCP audience- Token passed directly to Nextcloud via
get_client_from_context() - Nextcloud validates its own audience when receiving API calls
Implementation:
unified_verifier.py:185-252-_verify_mcp_audience()validates MCP audience onlycontext.py:96-99- Uses token directly in multi-audience mode
Background Sync
Requires ENABLE_OFFLINE_ACCESS=true. Uses stored refresh tokens to obtain access tokens for background operations.
MCP Server Nextcloud OIDC
│ │
[Background Job starts] │ │
│── Get refresh token ──────▶│
│ (from encrypted storage) │
│ │
│── Token refresh request ──▶│
│ grant_type=refresh_token │
│ scope=openid profile ... │
│◀── New access + refresh ───│
│ (rotation) │
│ │
│── Store rotated refresh ──▶│
│ (encrypted) │
│ │
│── HTTP + Access Token ────▶│
│ Authorization: Bearer │
│◀── API Response ───────────│
Key characteristics:
- Refresh tokens stored encrypted in SQLite (
TOKEN_STORAGE_DB) - Nextcloud OIDC rotates refresh tokens on every use (one-time use)
TokenBrokerServicehandles token lifecycle- Per-user locking prevents race conditions during concurrent refresh
Implementation:
token_broker.py:269-362-get_background_token()handles refresh with lockingtoken_broker.py:428-509-_refresh_access_token_with_scopes()exchanges refresh token
Astrolabe → MCP Server
Same as Multi-User BasicAuth. See Astrolabe → MCP Server above.
4. OAuth Token Exchange (RFC 8693)
Use Case: Multi-user deployment where MCP tokens are separate from Nextcloud tokens. Provides stronger security boundaries.
Enabled by ENABLE_TOKEN_EXCHANGE=true.
MCP Client → MCP Server → Nextcloud
MCP Client MCP Server Nextcloud OIDC
│ │ │
│── Bearer Token ────────────▶│ │
│ aud: "mcp-server" │ │
│ (MCP audience only) │ │
│ │── Validate MCP audience ──▶│
│ │ │
│ │── RFC 8693 Exchange ──────▶│
│ │ grant_type= │
│ │ urn:ietf:params:oauth: │
│ │ grant-type:token-exchange
│ │ subject_token=<mcp-token>│
│ │ requested_audience= │
│ │ "nextcloud" │
│ │◀── Delegated Token ────────│
│ │ aud: "nextcloud" │
│ │ │
│ │── HTTP + Delegated Token ─▶│
│ │ Authorization: Bearer │
│ │◀── API Response ───────────│
│◀── Tool Result ─────────────│ │
Key characteristics:
- Strict audience separation: MCP token has
aud: "mcp-server"only - Server exchanges for Nextcloud-audience token on each request
- Ephemeral delegated tokens (not cached by default)
- Strongest security boundary between MCP and Nextcloud access
Token exchange details:
- Uses RFC 8693 "urn:ietf:params:oauth:grant-type:token-exchange"
- Subject token: MCP access token
- Requested audience: Nextcloud resource URI
- Result: Short-lived token scoped for Nextcloud
Implementation:
token_broker.py:220-267-get_session_token()performs on-demand exchangetoken_exchange.py-exchange_token_for_delegation()implements RFC 8693context.py:88-94- Routes to session client in exchange mode
Background Sync
Same as OAuth Single-Audience. Uses stored refresh tokens from Flow 2 provisioning.
MCP Server Nextcloud OIDC
│ │
[User provisions access] │ │
│── Flow 2 OAuth ───────────▶│
│ client_id="mcp-server" │
│ scope=offline_access ... │
│◀── Refresh Token ──────────│
│ (stored encrypted) │
│ │
[Background Job runs later] │ │
│── Refresh for background ─▶│
│ (same as single-audience)│
Key difference from interactive:
- Interactive: On-demand token exchange per request
- Background: Uses pre-provisioned refresh tokens (Flow 2)
Astrolabe → MCP Server
Same as Multi-User BasicAuth. See Astrolabe → MCP Server above.
Configuration Quick Reference
Single-User BasicAuth
NEXTCLOUD_HOST=http://localhost:8080
NEXTCLOUD_USERNAME=admin
NEXTCLOUD_PASSWORD=password
Multi-User BasicAuth
NEXTCLOUD_HOST=http://nextcloud.example.com
ENABLE_MULTI_USER_BASIC_AUTH=true
# Optional: For background sync
ENABLE_OFFLINE_ACCESS=true
TOKEN_ENCRYPTION_KEY=<32-byte-key>
TOKEN_STORAGE_DB=/data/tokens.db
OAuth Single-Audience (Default)
NEXTCLOUD_HOST=http://nextcloud.example.com
# No username/password triggers OAuth mode
# Optional: Static client credentials (instead of DCR)
NEXTCLOUD_OIDC_CLIENT_ID=<client-id>
NEXTCLOUD_OIDC_CLIENT_SECRET=<client-secret>
# Optional: For background sync
ENABLE_OFFLINE_ACCESS=true
TOKEN_ENCRYPTION_KEY=<32-byte-key>
TOKEN_STORAGE_DB=/data/tokens.db
OAuth Token Exchange
NEXTCLOUD_HOST=http://nextcloud.example.com
ENABLE_TOKEN_EXCHANGE=true
NEXTCLOUD_OIDC_CLIENT_ID=<client-id>
NEXTCLOUD_OIDC_CLIENT_SECRET=<client-secret>
# Optional: For background sync
ENABLE_OFFLINE_ACCESS=true
TOKEN_ENCRYPTION_KEY=<32-byte-key>
TOKEN_STORAGE_DB=/data/tokens.db
Related Documentation
- Authentication - Configuration details and setup guides
- OAuth Architecture - Deep OAuth protocol details
- ADR-004: Progressive Consent - Dual OAuth flow architecture
- ADR-005: Token Audience Validation - Audience validation strategy
- ADR-018: Nextcloud PHP App - Astrolabe integration
- ADR-020: Deployment Modes - Mode detection and validation