Implement the ADR-004 Hybrid Flow OAuth pattern where the MCP server intercepts the OAuth callback to obtain master refresh tokens while maintaining PKCE security for clients. ## Implementation ### OAuth Routes (ADR-004 Hybrid Flow) - Add `/oauth/authorize` endpoint: Intercepts client OAuth initiation - Add `/oauth/callback` endpoint: Receives IdP callback, stores master token - Add `/oauth/token` endpoint: Exchanges MCP code for client access token - Implement PKCE code challenge/verifier validation - Store OAuth sessions with state/challenge correlation ### MCP Server Integration - Update `setup_oauth_config()` to return client_id and client_secret - Initialize OAuth context in Starlette lifespan for login routes - Add OAuth session storage to RefreshTokenStorage - Configure authlib dependency for OAuth flow management ### Integration Tests - Create `test_adr004_hybrid_flow.py` with Playwright automation - Add `adr004_hybrid_flow_mcp_client` session-scoped fixture - Test MCP session establishment with hybrid flow token - Test tool execution using stored refresh tokens (on-behalf-of pattern) - Test persistent access across multiple operations - All tests passing: ✅ 3 passed in 8.82s ### Documentation - Update ADR-004 with comprehensive Testing section - Add integration test commands and coverage details - Document test implementation and verification steps - Create TESTING_INSTRUCTIONS.md for manual and automated testing - Include manual test scripts for reference/debugging ## What This Enables ✅ PKCE code challenge/verifier flow ✅ MCP server intercepts OAuth callback and stores master refresh token ✅ Client receives MCP access token (not master token) ✅ MCP session establishment with hybrid flow token ✅ Tool execution using stored refresh tokens (on-behalf-of pattern) ✅ Multiple operations without re-authentication ✅ Proper token isolation (client never sees master token) ## Testing Run ADR-004 integration tests: ```bash uv run pytest tests/server/oauth/test_adr004_hybrid_flow.py --browser firefox -v ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
6.3 KiB
ADR-004 OAuth Flow Testing Instructions
Automated Integration Test (Recommended)
The ADR-004 Hybrid Flow is now fully tested via automated integration tests using Playwright:
# Run all ADR-004 tests
uv run pytest tests/server/oauth/test_adr004_hybrid_flow.py --browser firefox -v
# Run specific test
uv run pytest tests/server/oauth/test_adr004_hybrid_flow.py::test_adr004_hybrid_flow_tool_execution --browser firefox -v
These tests verify:
- ✅ PKCE code challenge/verifier flow
- ✅ MCP server intercepts OAuth callback
- ✅ Master refresh token storage
- ✅ Client receives MCP access token
- ✅ MCP session establishment with hybrid flow token
- ✅ Tool execution using stored refresh tokens
- ✅ Multiple operations without re-authentication
Manual Test (Legacy)
For manual testing or debugging, you can use the standalone test script:
# Make sure port 8765 is available
lsof -ti:8765 | xargs kill -9 2>/dev/null
# Run the test
uv run python tests/manual/test_adr004_manual.py --provider nextcloud
Expected Flow
1. Test Script Starts
======================================================================
ADR-004 MANUAL OAUTH FLOW TEST
======================================================================
Provider: nextcloud
MCP Server: http://localhost:8001
Nextcloud: http://localhost:8080
======================================================================
✓ Generated PKCE challenge: gxQLsYDJ...
✓ Started callback server at http://localhost:8765/callback
2. Open OAuth URL in Browser
The script will print:
======================================================================
STEP 1: AUTHORIZE THE MCP SERVER
======================================================================
📋 Open this URL in your browser:
http://localhost:8001/oauth/authorize?response_type=code&...
📌 What will happen:
1. You'll be redirected to Nextcloud/Keycloak login
2. Login with username: admin, password: admin
3. You'll see a consent screen asking to authorize the MCP server
4. Click 'Authorize' or 'Allow'
5. You'll be redirected to localhost:8765/callback
6. The authorization code will appear in the terminal
3. Browser Flow
- Nextcloud Login - You see the Nextcloud login page
- Enter Credentials - admin/admin
- Consent Screen - "Authorize Nextcloud MCP Server (jwt) to access your account?"
- Click Authorize
- Redirect Chain:
- Nextcloud redirects to:
http://localhost:8001/oauth/callback?code=... - MCP server processes the code
- MCP server redirects to:
http://localhost:8765/callback?code=mcp-code-...&state=... - Browser reaches the test script's callback server
- You see: "✓ Authorization Successful - You can close this window"
- Nextcloud redirects to:
4. Test Script Continues
✓ Received authorization code!
Code: mcp-code-xyz...
✓ State parameter verified (CSRF protection)
======================================================================
STEP 2: EXCHANGE CODE FOR ACCESS TOKEN
======================================================================
✓ Successfully received access token
Token: eyJhbGciOiJSUzI1Ni...
Type: Bearer
Expires: 3600s
======================================================================
STEP 3: CALL MCP TOOL WITH ACCESS TOKEN
======================================================================
✓ MCP tool call succeeded!
Result: {...}
======================================================================
🎉 ADR-004 OAUTH FLOW TEST - SUCCESS
======================================================================
Troubleshooting
Browser Gets Stuck at "localhost:8765 refused to connect"
Problem: The callback server on port 8765 isn't accessible.
Solutions:
- Check firewall isn't blocking port 8765
- Verify the test script is still running
- Check another process isn't using port 8765:
lsof -ti:8765
Browser Shows "localhost:8765 - ERR_CONNECTION_REFUSED"
Problem: The callback server stopped or never started.
Solution:
- Check the test script output - it should say "✓ Started callback server"
- Restart the test script
- Manually test the callback server:
Should return HTML page with "Authorization Successful"
curl http://localhost:8765/callback?code=test&state=test
"Session not found or expired" Error
Problem: Took too long between steps (>10 minutes).
Solution: Restart the test - sessions expire after 10 minutes.
Client ID is None
Problem: OAuth client credentials not loaded.
Solution: Rebuild the MCP server:
docker-compose up --build -d mcp-oauth
Nextcloud Shows "Invalid redirect_uri"
Problem: The redirect URI isn't registered for the OAuth client.
Solution: Check registered URIs:
docker compose exec db mariadb -u root -ppassword nextcloud -e \
"SELECT c.client_identifier, r.redirect_uri FROM oc_oidc_clients c \
LEFT JOIN oc_oidc_redirect_uris r ON c.id = r.client_id \
WHERE c.name LIKE '%MCP%';"
Should show: http://localhost:8001/oauth/callback
Manual Test Without Script
If the automated test doesn't work, you can test manually:
-
Start callback server manually:
python3 -m http.server 8765 -
Open OAuth URL in browser (get from test script output or build manually):
http://localhost:8001/oauth/authorize?response_type=code&client_id=test-mcp-client&redirect_uri=http://localhost:8765/callback&scope=openid+profile+email+offline_access&state=TEST&code_challenge=CHALLENGE&code_challenge_method=S256 -
Complete login at Nextcloud
-
Browser should redirect to
http://localhost:8765/callback?code=mcp-code-...&state=TEST -
Copy the code from the URL and exchange it:
curl -X POST http://localhost:8001/oauth/token \ -d "grant_type=authorization_code" \ -d "code=<MCP_CODE_HERE>" \ -d "code_verifier=<VERIFIER_HERE>" \ -d "redirect_uri=http://localhost:8765/callback" \ -d "client_id=test-mcp-client"
Expected Database State After Success
# Check refresh token was stored
docker compose exec mcp-oauth sh -c \
"sqlite3 /app/data/tokens.db 'SELECT user_id, created_at FROM refresh_tokens;'"
Should show an entry for the authenticated user.