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:
Chris Coutinho
2026-06-14 11:37:07 +02:00
co-authored by Claude Opus 4.8
parent d0f9f2daa5
commit 64e50c0bcc
6 changed files with 102 additions and 103 deletions
+48 -3
View File
@@ -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.