This guide explains how to test the OIDC consent feature using the development version of the OIDC app mounted into the Docker environment.
The development OIDC app is mounted from ~/Software/oidc into the container at /opt/apps/oidc:
# docker-compose.yml
volumes:
- ../Software/oidc:/opt/apps/oidc:roWhy mount outside /var/www/html/?
- The Nextcloud container uses
rsyncto initialize/var/www/html/from the image - Mounting inside that path causes conflicts (rsync tries to delete mounted directories)
- Mounting to
/opt/apps/oidcavoids rsync entirely - Nextcloud supports multiple app directories via the
apps_pathsconfiguration
How multiple app paths work:
- Nextcloud can load apps from multiple directories
- The post-installation hook registers
/opt/appsas an additional app directory (index 2) - Apps in default paths (index 0 and 1) are still available
- All directories are scanned for apps, but
/opt/appsis read-only
This setup allows you to:
- Test changes without rebuilding containers
- Avoid needing npm/node in the container (JS already built on host)
- Iterate quickly on development
- Install other Nextcloud apps normally (custom_apps remains writable)
- Mount Development App: Docker mounts
~/Software/oidcto/opt/apps/oidc(outside Nextcloud's path) - Register App Path: The
10-install-oidc-app.shhook configures/opt/appsas an additional app directory - Enable App: The hook enables the OIDC app from
/opt/apps/oidc - Run Migrations: Nextcloud detects pending migrations and runs them automatically
- Configure OIDC: Dynamic client registration and PKCE are enabled
cd ~/Projects/nextcloud-mcp-server
# Start fresh (recommended for first test)
docker compose down -v
docker compose up -d
# Wait for initialization (check logs)
docker compose logs -f appThe post-installation hooks will:
- Configure custom_apps path (already done)
- Enable OIDC app from mounted directory
- Run database migrations (including consent table creation)
- Configure OIDC settings
Before running docker compose up -d, the consent feature will NOT be active:
- ❌ No
oc_oidc_user_consentstable in database - ❌ Migration 0015 not applied yet
- ❌ ConsentController class not loaded
- ❌ Consent routes not registered
You can verify this with:
# Check migrations applied (should stop at 0014)
docker compose exec -T db mariadb -u nextcloud -ppassword nextcloud -e "SELECT version FROM oc_migrations WHERE app = 'oidc' ORDER BY version DESC LIMIT 3;" nextcloud
# Check for consent table (should return empty)
docker compose exec -T db mariadb -u nextcloud -ppassword nextcloud -e "SHOW TABLES LIKE 'oc_oidc_user_consents';" nextcloudAfter docker compose up -d with the mounted OIDC directory, the consent feature should be active:
- ✅
oc_oidc_user_consentstable exists - ✅ Migration 0015 (Version0015Date20251123100100) applied
- ✅ ConsentController routes registered
- ✅ Consent screen appears during OAuth flows
docker compose exec app php occ app:list | grep -A 2 oidcExpected output:
- oidc: 1.10.0 (enabled)
Verify that /opt/apps is registered as an additional app directory:
# Check configured app paths
docker compose exec app php occ config:system:get apps_paths
# Verify the mount is accessible
docker compose exec app ls -la /opt/apps/oidc/
# Verify custom_apps is writable (for normal app installation)
docker compose exec -u www-data app touch /var/www/html/custom_apps/.test && echo "✅ custom_apps is writable" || echo "❌ custom_apps NOT writable"
docker compose exec app rm -f /var/www/html/custom_apps/.testExpected: Output should show multiple app paths including index 2 (/opt/apps).
# Check controller exists in mounted location
docker compose exec app ls -la /opt/apps/oidc/lib/Controller/ConsentController.php
# Check Vue component exists
docker compose exec app ls -la /opt/apps/oidc/src/Consent.vue
# Check built JS exists
docker compose exec app ls -lh /opt/apps/oidc/js/oidc-consent.jsNote: These checks will only pass after restarting containers with the mounted OIDC app.
# Check if consent table exists
docker compose exec -T db mariadb -u nextcloud -ppassword nextcloud -e "SHOW TABLES LIKE 'oc_oidc_user_consents';"
# Check table structure
docker compose exec -T db mariadb -u nextcloud -ppassword nextcloud -e "DESCRIBE oc_oidc_user_consents;"
# Verify migration 0015 was applied
docker compose exec -T db mariadb -u nextcloud -ppassword nextcloud -e "SELECT app, version FROM oc_migrations WHERE app = 'oidc' AND version LIKE '%0015%';"Expected table structure:
- id: int(10) unsigned, auto_increment, primary key
- user_id: varchar(256), not null
- client_id: int(10) unsigned, not null
- scopes_granted: varchar(512), not null
- created_at: int(10) unsigned, not null
- updated_at: int(10) unsigned, not null
- expires_at: int(10) unsigned, nullable
docker compose exec app php occ router:list | grep consentExpected output:
oidc.Consent.show GET apps/oidc/consent
oidc.Consent.grant POST apps/oidc/consent/grant
oidc.Consent.deny POST apps/oidc/consent/deny
The JWT client is automatically created by the post-installation hooks:
# Check if JWT client exists
docker compose exec app cat /var/www/html/.oauth-jwt/nextcloud_oauth_client.jsonYou can test using the MCP OAuth container or manually:
Option A: Using MCP OAuth container
# The mcp-oauth container will trigger the OAuth flow
docker compose logs -f mcp-oauthOption B: Manual browser test
- Get client_id from the JWT client JSON
- Visit in browser:
http://localhost:8080/apps/oidc/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=http://localhost:8001/oauth/callback&scope=openid+profile+email+notes.read+notes.write&state=test123
First Authorization:
- User logs in (if not already authenticated)
- Consent screen appears with:
- Application name: "Nextcloud MCP Server JWT"
- List of requested scopes with descriptions:
- ✓ Basic authentication (openid) - required, cannot deselect
- ✓ Profile information (profile)
- ✓ Email address (email)
- ✓ notes.read (custom scope, shown as-is)
- ✓ notes.write (custom scope, shown as-is)
- "Allow" and "Deny" buttons
- User selects scopes and clicks "Allow"
- Authorization proceeds with selected scopes
- Consent is stored in database
Subsequent Authorizations:
- Same scopes → No consent screen (uses stored consent)
- Different scopes → Consent screen appears again
- If user clicks "Deny" → Returns
error=access_deniedto client
After granting consent:
# View all stored consents with formatted timestamps
docker compose exec -T db mariadb -u nextcloud -ppassword nextcloud -e "
SELECT
user_id,
client_id,
scopes_granted,
FROM_UNIXTIME(created_at) as created,
FROM_UNIXTIME(updated_at) as updated,
FROM_UNIXTIME(expires_at) as expires
FROM oc_oidc_user_consents;
" nextcloud
# Or for a compact view:
docker compose exec -T db mariadb -u nextcloud -ppassword nextcloud -e "SELECT * FROM oc_oidc_user_consents;" nextcloudCheck browser console (F12 → Console tab):
# Look for JS errors like:
Failed to load resource: js/oidc-consent.js
Check Nextcloud logs:
docker compose exec app tail -f /var/www/html/data/nextcloud.log | grep -i consentVerify JS file loaded:
# Check file exists and has correct size (~73KB)
docker compose exec app ls -lh /opt/apps/oidc/js/oidc-consent.jsClear Nextcloud caches:
docker compose exec app php occ maintenance:repair
docker compose restart appCheck which migrations have been applied:
docker compose exec -T db mariadb -u nextcloud -ppassword nextcloud -e "SELECT app, version FROM oc_migrations WHERE app = 'oidc' ORDER BY version;" nextcloudExpected to see Version0015Date20251123100100 in the list.
Manually trigger migrations:
# Disable and re-enable app (triggers all pending migrations)
docker compose exec app php occ app:disable oidc
docker compose exec app php occ app:enable oidc
# Verify migration 0015 was applied
docker compose exec -T db mariadb -u nextcloud -ppassword nextcloud -e "SELECT version FROM oc_migrations WHERE app = 'oidc' AND version LIKE '%0015%';" nextcloudIf router:list doesn't show consent routes:
# The autoloader might not have picked up new classes
# Restart the container
docker compose restart app
# Wait for it to be ready
sleep 10
# Try again
docker compose exec app php occ router:list | grep consentIf still not working, check if ConsentController is accessible:
docker compose exec app php -r "
require_once '/var/www/html/lib/base.php';
\$class = 'OCA\\OIDCIdentityProvider\\Controller\\ConsentController';
if (class_exists(\$class)) {
echo \"Class exists\n\";
} else {
echo \"Class not found\n\";
}
"- Edit source file on host:
cd ~/Software/oidc
# Edit src/Consent.vue- Rebuild JS:
npm run build- Refresh browser (container sees changes immediately via volume mount at /opt/apps/oidc)
- Edit files on host:
cd ~/Software/oidc
# Edit lib/Controller/ConsentController.php or other PHP files-
Changes are immediately visible (PHP is interpreted, no build step)
-
For new classes or major changes, restart container:
docker compose restart appIf you modify the migration:
# Changes won't be picked up if migration already ran
# Need to recreate the database:
docker compose down -v # Removes volumes
docker compose up -d # Fresh start with clean DBcd ~/Projects/nextcloud-mcp-server
docker compose down -vThis removes:
- All containers
- Database volume (all data)
- OAuth client credentials
docker compose restart appThis preserves:
- Database (consents, clients, users)
- OAuth client credentials
- Make changes in
~/Software/oidc - Build JS if you changed Vue files:
npm run build - Test immediately - refresh browser or restart container
- No need to rebuild Docker images or reinstall app
- Iterate quickly with instant feedback
When ready to deploy:
-
Create patch file (already done):
cd ~/Software/oidc git format-patch master --stdout > user-consent-feature.patch
-
Test patch in clean environment:
# In a production-like environment cd /path/to/production/oidc git apply user-consent-feature.patch npm install npm run build php occ app:disable oidc php occ app:enable oidc
-
Verify migration runs automatically on app enable
-
Submit pull request to upstream repository