docs(login-flow): require static OIDC client; remove dead OAuth env samples
Self-hosting login_flow against Nextcloud's built-in `oidc` app breaks after ~1h when relying on the DCR fallback: the `oidc` app deletes dynamically-registered clients after `client_expire_time` (default 3600s), pruning on every /authorize. The MCP server caches the now-deleted client, so authorize/refresh fail with an "Access forbidden" page permanently — surviving server restart and connector recreation (issue #907). - docs/login-flow-v2.md: add "Default IdP setup (Nextcloud oidc app)" with static-client steps, and a Troubleshooting entry for the #907 symptom/fix; reframe the OIDC-client env vars as strongly recommended. - docs/configuration.md: promote NEXTCLOUD_OIDC_CLIENT_ID/_SECRET to strongly recommended with a DCR-expiry warning; add them to the login_flow example. - docker-compose.yml: clarify the DCR caveat and point self-hosters to a static client for login_flow / background sync. - env.sample.oauth-multi-user: fix the removed `oauth_single_audience` value (now login_flow) and require a static OIDC client. - env.sample.oauth-advanced: remove — it configured the removed OAuth token-exchange mode (no implementation remains; the mode value now errors at startup). Drop its references in configuration.md / configuration-migration-v2.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
d0f9f2daa5
commit
64e50c0bcc
+12
-2
@@ -177,8 +177,10 @@ services:
|
||||
- VECTOR_SYNC_SCAN_INTERVAL=30
|
||||
- VECTOR_SYNC_PROCESSOR_WORKERS=1
|
||||
|
||||
# OAuth credentials for background sync (optional - uses DCR if not provided)
|
||||
# Uncomment to avoid DCR:
|
||||
# OAuth client for the MCP server's IdP registration. Falls back to DCR
|
||||
# if unset — but with Nextcloud's built-in `oidc` app, DCR clients expire
|
||||
# after ~1h (client_expire_time) and break auth permanently (see #907).
|
||||
# For any non-CI deployment, register a static client and set these:
|
||||
# - NEXTCLOUD_OIDC_CLIENT_ID=your_client_id
|
||||
# - NEXTCLOUD_OIDC_CLIENT_SECRET=your_client_secret
|
||||
|
||||
@@ -293,6 +295,14 @@ services:
|
||||
# the browser-based app-password layer is derived automatically.
|
||||
- MCP_DEPLOYMENT_MODE=login_flow
|
||||
|
||||
# NOTE: this dev/test service relies on Dynamic Client Registration for
|
||||
# the MCP server's own OIDC client, which is fine for short-lived CI runs.
|
||||
# For a real self-hosted login_flow deployment, register a STATIC client
|
||||
# in Nextcloud (Administration → OpenID Connect provider) and set
|
||||
# NEXTCLOUD_OIDC_CLIENT_ID / NEXTCLOUD_OIDC_CLIENT_SECRET — the built-in
|
||||
# `oidc` app deletes DCR clients after ~1h, which breaks auth (see #907
|
||||
# and docs/login-flow-v2.md → Troubleshooting).
|
||||
|
||||
# Token storage (required for app password + session persistence).
|
||||
# Source the key from .env — see env.sample. To generate a fresh key:
|
||||
# python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
|
||||
|
||||
@@ -500,8 +500,7 @@ We provide mode-specific templates for new deployments:
|
||||
| Template | Use Case |
|
||||
|----------|----------|
|
||||
| `env.sample.single-user` | Simplest setup |
|
||||
| `env.sample.oauth-multi-user` | Recommended multi-user |
|
||||
| `env.sample.oauth-advanced` | Token exchange mode |
|
||||
| `env.sample.oauth-multi-user` | Recommended multi-user (Login Flow v2) |
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
|
||||
+22
-3
@@ -19,7 +19,7 @@ cp env.sample .env # Full reference with all options
|
||||
# Edit .env with your Nextcloud details
|
||||
```
|
||||
|
||||
> **Note:** The legacy templates `env.sample.oauth-multi-user` and `env.sample.oauth-advanced` configure the deprecated direct-OAuth-to-Nextcloud modes. New deployments should use [Login Flow v2](login-flow-v2.md) for multi-user setups.
|
||||
> **Note:** `env.sample.oauth-multi-user` is a Login Flow v2 quick-start template for multi-user setups. See [Login Flow v2](login-flow-v2.md).
|
||||
|
||||
Then choose your deployment mode:
|
||||
|
||||
@@ -97,6 +97,13 @@ MCP_DEPLOYMENT_MODE=login_flow
|
||||
TOKEN_ENCRYPTION_KEY=<fernet-key>
|
||||
TOKEN_STORAGE_DB=/app/data/tokens.db
|
||||
|
||||
# Static OIDC client for the MCP server's own IdP registration.
|
||||
# Strongly recommended — with Nextcloud's built-in oidc app the DCR
|
||||
# fallback expires after ~1h (see the warning below). Create the client
|
||||
# under Administration settings → OpenID Connect provider.
|
||||
NEXTCLOUD_OIDC_CLIENT_ID=<client-id-from-nextcloud>
|
||||
NEXTCLOUD_OIDC_CLIENT_SECRET=<client-secret-from-nextcloud>
|
||||
|
||||
# Public URLs for browser redirects
|
||||
NEXTCLOUD_MCP_SERVER_URL=https://mcp.example.com
|
||||
NEXTCLOUD_PUBLIC_ISSUER_URL=https://your.nextcloud.instance.com
|
||||
@@ -110,10 +117,22 @@ NEXTCLOUD_PUBLIC_ISSUER_URL=https://your.nextcloud.instance.com
|
||||
| `TOKEN_STORAGE_DB` | ✅ Yes | Path to SQLite DB for stored app passwords (use a persistent volume) |
|
||||
| `NEXTCLOUD_MCP_SERVER_URL` | ✅ Yes | Public URL of the MCP server (used as the audience claim and for browser redirects) |
|
||||
| `NEXTCLOUD_PUBLIC_ISSUER_URL` | ✅ Yes | Public URL of Nextcloud (for browser redirects during Login Flow v2) |
|
||||
| `NEXTCLOUD_OIDC_CLIENT_ID` | ⚠️ Optional (preferred) | OIDC client ID for the MCP server's relying-party registration with the IdP (Nextcloud OIDC by default; Keycloak / Cognito / etc. via `OIDC_DISCOVERY_URL`). If unset and the IdP advertises a `registration_endpoint`, RFC 7591 DCR is used as fallback. |
|
||||
| `NEXTCLOUD_OIDC_CLIENT_SECRET` | ⚠️ Optional (preferred) | OIDC client secret paired with `NEXTCLOUD_OIDC_CLIENT_ID`. |
|
||||
| `NEXTCLOUD_OIDC_CLIENT_ID` | ✅ Strongly recommended | OIDC client ID for the MCP server's relying-party registration with the IdP (Nextcloud's built-in OIDC by default; Keycloak / Cognito / etc. via `OIDC_DISCOVERY_URL`). If unset and the IdP advertises a `registration_endpoint`, the server falls back to RFC 7591 Dynamic Client Registration (DCR) — **but with Nextcloud's built-in `oidc` app this fallback breaks after ~1 hour** (see warning below). Create a static client and set this instead. |
|
||||
| `NEXTCLOUD_OIDC_CLIENT_SECRET` | ✅ Strongly recommended | OIDC client secret paired with `NEXTCLOUD_OIDC_CLIENT_ID`. |
|
||||
| `OIDC_DISCOVERY_URL` | Optional | Override the IdP discovery URL. Defaults to `${NEXTCLOUD_HOST}/.well-known/openid-configuration` (Nextcloud's built-in OIDC). Set to a Keycloak realm or AWS Cognito user-pool discovery URL to use an external IdP. |
|
||||
|
||||
> **⚠️ Use a static OIDC client with Nextcloud's built-in `oidc` app.** If you
|
||||
> don't set `NEXTCLOUD_OIDC_CLIENT_ID` / `NEXTCLOUD_OIDC_CLIENT_SECRET`, the MCP
|
||||
> server registers its own relying-party client via DCR. Nextcloud's `oidc` app
|
||||
> treats DCR clients as **ephemeral** and deletes them after `client_expire_time`
|
||||
> (default **3600s = 1 hour**), pruning on every `/authorize`. Once it's gone,
|
||||
> authorization and token refresh fail and users hit an **"Access forbidden"**
|
||||
> page — permanently, because the server keeps reusing the deleted client.
|
||||
> Register a permanent client in **Administration settings → OpenID Connect
|
||||
> provider** and set the two env vars. See
|
||||
> [Login Flow v2 → Troubleshooting](login-flow-v2.md#troubleshooting) and
|
||||
> [issue #907](https://github.com/cbcoutinho/nextcloud-mcp-server/issues/907).
|
||||
|
||||
See [Login Flow v2](login-flow-v2.md) for full setup, scope reference, and troubleshooting.
|
||||
|
||||
---
|
||||
|
||||
+48
-3
@@ -52,9 +52,12 @@ NEXTCLOUD_HOST=https://your.nextcloud.example.com
|
||||
|
||||
# OIDC client credentials for the MCP server's relying-party relationship with the IdP.
|
||||
# These are generic OIDC client credentials — they work with any OIDC provider, despite
|
||||
# the Nextcloud-flavored env-var names. Preferred path: register a client in your IdP
|
||||
# (Nextcloud admin → OIDC, Keycloak realm → Clients, etc.) and set these. If both are
|
||||
# unset and the IdP advertises a `registration_endpoint`, the server falls back to RFC 7591 DCR.
|
||||
# the Nextcloud-flavored env-var names. Register a static client in your IdP
|
||||
# (Nextcloud admin → OpenID Connect provider, Keycloak realm → Clients, etc.) and set these.
|
||||
#
|
||||
# Strongly recommended — do NOT rely on the DCR fallback with Nextcloud's built-in
|
||||
# `oidc` app: it deletes dynamically-registered clients after ~1h, which breaks the
|
||||
# connection permanently (see Troubleshooting → "Access forbidden" below).
|
||||
NEXTCLOUD_OIDC_CLIENT_ID=<your-client-id>
|
||||
NEXTCLOUD_OIDC_CLIENT_SECRET=<your-client-secret>
|
||||
|
||||
@@ -72,6 +75,23 @@ NEXTCLOUD_PUBLIC_ISSUER_URL=https://your.nextcloud.example.com # Public URL of
|
||||
|
||||
When using an external IdP (Keycloak, Cognito, etc.), see [Keycloak Multi-Client Token Validation](keycloak-multi-client-validation.md) for how Nextcloud's `user_oidc` app handles realm-level token validation if you also federate Nextcloud's own login through the same IdP.
|
||||
|
||||
### Default IdP setup (Nextcloud's built-in `oidc` app)
|
||||
|
||||
When `OIDC_DISCOVERY_URL` is unset, Nextcloud's own **OpenID Connect provider**
|
||||
(`oidc`) app is the IdP. Register a **static** client for the MCP server there —
|
||||
don't rely on Dynamic Client Registration, because the `oidc` app auto-deletes
|
||||
DCR clients after ~1 hour (see [Troubleshooting](#access-forbidden-after-the-connection-worked-for-a-while)).
|
||||
|
||||
1. Install/enable the **OpenID Connect provider** (`oidc`) app.
|
||||
2. Go to **Administration settings → OpenID Connect provider → Add client** and set:
|
||||
- **Redirect URI:** `https://<your-mcp-server>/oauth/callback`
|
||||
- **Flow / response type:** authorization **code**
|
||||
- **Type:** **confidential** (so it issues a client secret)
|
||||
- **Resource identifier:** `https://<your-mcp-server>/mcp` (so issued tokens carry the MCP server's audience)
|
||||
- **Scopes:** leave empty to allow all, or list the per-app scopes you want plus `openid profile email offline_access`
|
||||
3. Copy the generated client ID and secret into `NEXTCLOUD_OIDC_CLIENT_ID` /
|
||||
`NEXTCLOUD_OIDC_CLIENT_SECRET`.
|
||||
|
||||
### External IdP setup (Authentik / Keycloak / Cognito)
|
||||
|
||||
When `OIDC_DISCOVERY_URL` points at a third-party IdP rather than Nextcloud's own
|
||||
@@ -329,6 +349,31 @@ JWTs are preferred for production because validation is local and stateless. Opa
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Access forbidden" after the connection worked for a while
|
||||
|
||||
**Symptom:** authentication succeeds and tools work for a while (often up to an
|
||||
hour), then the connection silently drops. Re-connecting redirects to Nextcloud
|
||||
and shows an **"Access forbidden"** page. Restarting the MCP server and
|
||||
re-creating the MCP client/connector don't help. ([#907](https://github.com/cbcoutinho/nextcloud-mcp-server/issues/907))
|
||||
|
||||
**Cause:** you didn't set `NEXTCLOUD_OIDC_CLIENT_ID` / `NEXTCLOUD_OIDC_CLIENT_SECRET`,
|
||||
so the MCP server registered *its own* relying-party client with Nextcloud's
|
||||
built-in `oidc` app via Dynamic Client Registration (DCR). The `oidc` app treats
|
||||
DCR clients as ephemeral and **deletes them after `client_expire_time` (default
|
||||
3600s = 1 hour)** — it prunes expired DCR clients on every `/authorize` request.
|
||||
Once the server's client is gone, `/authorize` can't find it (→ the "Access
|
||||
forbidden" page) and token refresh fails too. It's permanent because the server
|
||||
cached that now-deleted client in `tokens.db` and keeps reusing it.
|
||||
|
||||
**Fix:** register a **static** (admin-created, non-DCR) client and configure it —
|
||||
see [Default IdP setup](#default-idp-setup-nextclouds-built-in-oidc-app). Static
|
||||
clients are never auto-deleted. Set `NEXTCLOUD_OIDC_CLIENT_ID` /
|
||||
`NEXTCLOUD_OIDC_CLIENT_SECRET` (they take precedence over the cached DCR client)
|
||||
and recreate the container.
|
||||
|
||||
As a non-recommended stopgap you can extend the DCR client lifetime globally:
|
||||
`occ config:app:set oidc client_expire_time --value 31536000`.
|
||||
|
||||
### "Provisioning loop" — user keeps being asked to authorize
|
||||
|
||||
Check that `TOKEN_STORAGE_DB` is on a persistent volume. The default (`/tmp` or per-process tempfile) is wiped on container restart, so each restart loses every stored app password.
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
# ============================================
|
||||
# OAUTH TOKEN EXCHANGE QUICK START (Advanced)
|
||||
# ============================================
|
||||
# Advanced OAuth deployment with RFC 8693 token exchange
|
||||
# Use for: Deployments requiring separate MCP and Nextcloud tokens
|
||||
# Features: Dual-audience tokens, enhanced security boundaries
|
||||
#
|
||||
# Copy this file to .env and configure
|
||||
|
||||
# ===== REQUIRED SETTINGS =====
|
||||
# Your Nextcloud instance URL (without trailing slash)
|
||||
NEXTCLOUD_HOST=https://nextcloud.example.com
|
||||
|
||||
# Enable token exchange mode
|
||||
ENABLE_TOKEN_EXCHANGE=true
|
||||
|
||||
# ===== REQUIRED: LEAVE USERNAME/PASSWORD EMPTY =====
|
||||
# OAuth mode activates when these are NOT set
|
||||
NEXTCLOUD_USERNAME=
|
||||
NEXTCLOUD_PASSWORD=
|
||||
|
||||
# ===== OPTIONAL: EXPLICIT MODE DECLARATION =====
|
||||
# Recommended for clarity
|
||||
MCP_DEPLOYMENT_MODE=oauth_token_exchange
|
||||
|
||||
# ===== OPTIONAL: PRE-REGISTERED OAUTH CLIENT =====
|
||||
# If you pre-register the OAuth client instead of using DCR:
|
||||
#NEXTCLOUD_OIDC_CLIENT_ID=your-client-id
|
||||
#NEXTCLOUD_OIDC_CLIENT_SECRET=your-client-secret
|
||||
|
||||
# MCP Server URL (for OAuth redirects)
|
||||
NEXTCLOUD_MCP_SERVER_URL=http://localhost:8000
|
||||
|
||||
# ===== OPTIONAL: TOKEN EXCHANGE TUNING =====
|
||||
# Cache TTL for exchanged tokens (default: 300 seconds = 5 minutes)
|
||||
TOKEN_EXCHANGE_CACHE_TTL=300
|
||||
|
||||
# ===== OPTIONAL: SEMANTIC SEARCH =====
|
||||
# AI-powered semantic search with automatic background operation setup
|
||||
#
|
||||
# Note: ENABLE_SEMANTIC_SEARCH automatically enables background operations
|
||||
# in token exchange mode, just like in OAuth single-audience mode
|
||||
#
|
||||
ENABLE_SEMANTIC_SEARCH=true
|
||||
|
||||
# Vector Database (required for semantic search)
|
||||
QDRANT_URL=http://qdrant:6333
|
||||
|
||||
# Embedding Provider (required for semantic search)
|
||||
OLLAMA_BASE_URL=http://ollama:11434
|
||||
OLLAMA_EMBEDDING_MODEL=nomic-embed-text
|
||||
|
||||
# Token Storage (required for background operations - auto-enabled by semantic search)
|
||||
# Generate encryption key: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
|
||||
TOKEN_ENCRYPTION_KEY=your-encryption-key-here
|
||||
TOKEN_STORAGE_DB=/app/data/tokens.db
|
||||
|
||||
# ===== OPTIONAL: DOCUMENT PROCESSING =====
|
||||
# Extract text from PDFs, images, DOCX for semantic search
|
||||
#ENABLE_DOCUMENT_PROCESSING=true
|
||||
#ENABLE_UNSTRUCTURED=true
|
||||
#UNSTRUCTURED_API_URL=http://unstructured:8000
|
||||
|
||||
# ===== TOKEN EXCHANGE MODE EXPLANATION =====
|
||||
# In this mode:
|
||||
# 1. MCP clients authenticate with tokens scoped to "mcp-server" audience
|
||||
# 2. Server exchanges MCP tokens for Nextcloud tokens on each request
|
||||
# 3. Provides clear separation between MCP session and Nextcloud access
|
||||
# 4. Enables fine-grained token lifecycle management
|
||||
#
|
||||
# When to use:
|
||||
# - Strict security requirements (separate token contexts)
|
||||
# - Complex multi-service architectures
|
||||
# - Need independent token expiration policies
|
||||
#
|
||||
# When NOT to use:
|
||||
# - Simple deployments (use oauth_single_audience instead)
|
||||
# - High-performance requirements (token exchange adds latency)
|
||||
|
||||
# For more configuration options, see env.sample
|
||||
+19
-13
@@ -1,10 +1,12 @@
|
||||
# ============================================
|
||||
# OAUTH MULTI-USER QUICK START (Recommended)
|
||||
# LOGIN FLOW v2 MULTI-USER QUICK START (Recommended)
|
||||
# ============================================
|
||||
# Multi-user deployment with OAuth authentication
|
||||
# Use for: Multi-user production deployments, enhanced security
|
||||
# Features: Single-audience tokens, automatic client registration (DCR)
|
||||
# Multi-user deployment with OAuth/OIDC authentication (ADR-022).
|
||||
# Use for: Multi-user production deployments, enhanced security.
|
||||
# The MCP server authenticates clients via OIDC and holds per-user
|
||||
# Nextcloud app passwords (encrypted) obtained via Login Flow v2.
|
||||
#
|
||||
# See docs/login-flow-v2.md for the full guide.
|
||||
# Copy this file to .env and configure
|
||||
|
||||
# ===== REQUIRED SETTINGS =====
|
||||
@@ -16,14 +18,17 @@ NEXTCLOUD_HOST=https://nextcloud.example.com
|
||||
NEXTCLOUD_USERNAME=
|
||||
NEXTCLOUD_PASSWORD=
|
||||
|
||||
# ===== OPTIONAL: EXPLICIT MODE DECLARATION =====
|
||||
# Recommended for clarity
|
||||
MCP_DEPLOYMENT_MODE=oauth_single_audience
|
||||
# ===== REQUIRED: DEPLOYMENT MODE =====
|
||||
MCP_DEPLOYMENT_MODE=login_flow
|
||||
|
||||
# ===== OPTIONAL: PRE-REGISTERED OAUTH CLIENT =====
|
||||
# If you pre-register the OAuth client instead of using DCR:
|
||||
#NEXTCLOUD_OIDC_CLIENT_ID=your-client-id
|
||||
#NEXTCLOUD_OIDC_CLIENT_SECRET=your-client-secret
|
||||
# ===== STRONGLY RECOMMENDED: STATIC OIDC CLIENT =====
|
||||
# Register a static client for the MCP server in your IdP and set these.
|
||||
# With Nextcloud's built-in `oidc` app you MUST do this: the DCR fallback
|
||||
# registers an ephemeral client that the app deletes after ~1h, which breaks
|
||||
# auth permanently ("Access forbidden" on reconnect — see issue #907).
|
||||
# Create one under Administration settings -> OpenID Connect provider.
|
||||
NEXTCLOUD_OIDC_CLIENT_ID=your-client-id
|
||||
NEXTCLOUD_OIDC_CLIENT_SECRET=your-client-secret
|
||||
|
||||
# MCP Server URL (for OAuth redirects)
|
||||
NEXTCLOUD_MCP_SERVER_URL=http://localhost:8000
|
||||
@@ -68,10 +73,11 @@ TOKEN_STORAGE_DB=/app/data/tokens.db
|
||||
# With ENABLE_SEMANTIC_SEARCH=true in OAuth mode:
|
||||
# ✅ Background operations enabled automatically
|
||||
# ✅ Refresh token storage enabled automatically
|
||||
# ✅ OAuth credentials required (DCR or pre-registered)
|
||||
# ✅ Static OIDC client credentials required (see above)
|
||||
# ✅ Encryption key required for token storage
|
||||
#
|
||||
# You only need to set ENABLE_SEMANTIC_SEARCH and provide the required
|
||||
# infrastructure (Qdrant, Ollama, encryption key). The rest is automatic!
|
||||
# infrastructure (static OIDC client, Qdrant, Ollama, encryption key).
|
||||
# The rest is automatic!
|
||||
|
||||
# For more advanced configuration, see env.sample
|
||||
|
||||
Reference in New Issue
Block a user