320 lines
15 KiB
Markdown
320 lines
15 KiB
Markdown
# OAuth Architecture
|
|
|
|
This document explains how OAuth2/OIDC authentication works in the Nextcloud MCP Server implementation.
|
|
|
|
## Overview
|
|
|
|
The Nextcloud MCP Server acts as an **OAuth 2.0 Resource Server**, protecting access to Nextcloud resources. It relies on Nextcloud's OIDC Identity Provider for user authentication and token validation.
|
|
|
|
## Architecture Diagram
|
|
|
|
```
|
|
┌─────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
|
│ │ │ │ │ │
|
|
│ MCP Client │ │ MCP Server │ │ Nextcloud │
|
|
│ (Claude, │ │ (Resource │ │ Instance │
|
|
│ etc.) │ │ Server) │ │ │
|
|
│ │ │ │ │ │
|
|
└──────┬──────┘ └────────┬─────────┘ └────────┬────────┘
|
|
│ │ │
|
|
│ │ │
|
|
│ 1. Connect to MCP │ │
|
|
├─────────────────────────────────>│ │
|
|
│ │ │
|
|
│ 2. Return auth settings │ │
|
|
│ (issuer_url, scopes) │ │
|
|
│<─────────────────────────────────┤ │
|
|
│ │ │
|
|
│ │ │
|
|
│ 3. Start OAuth flow (with PKCE) │ │
|
|
├──────────────────────────────────┼────────────────────────────────────>│
|
|
│ │ /apps/oidc/authorize │
|
|
│ │ │
|
|
│ 4. User authenticates in browser│ │
|
|
│<─────────────────────────────────┼─────────────────────────────────────┤
|
|
│ │ │
|
|
│ 5. Authorization code (redirect)│ │
|
|
│<─────────────────────────────────┤ │
|
|
│ │ │
|
|
│ 6. Exchange code for token │ │
|
|
├──────────────────────────────────┼────────────────────────────────────>│
|
|
│ │ /apps/oidc/token │
|
|
│ │ │
|
|
│ 7. Access token │ │
|
|
│<─────────────────────────────────┼─────────────────────────────────────┤
|
|
│ │ │
|
|
│ │ │
|
|
│ 8. API request with Bearer token│ │
|
|
├─────────────────────────────────>│ │
|
|
│ Authorization: Bearer xxx │ │
|
|
│ │ │
|
|
│ │ 9. Validate token via userinfo │
|
|
│ ├────────────────────────────────────>│
|
|
│ │ /apps/oidc/userinfo │
|
|
│ │ │
|
|
│ │ 10. User info (token valid) │
|
|
│ │<────────────────────────────────────┤
|
|
│ │ │
|
|
│ │ 11. Nextcloud API request │
|
|
│ ├────────────────────────────────────>│
|
|
│ │ Authorization: Bearer xxx │
|
|
│ │ (Notes, Calendar, etc.) │
|
|
│ │ │
|
|
│ │ 12. API response │
|
|
│ │<────────────────────────────────────┤
|
|
│ │ │
|
|
│ 13. MCP tool response │ │
|
|
│<─────────────────────────────────┤ │
|
|
│ │ │
|
|
```
|
|
|
|
## Components
|
|
|
|
### 1. MCP Client
|
|
- Any MCP-compatible client (Claude Desktop, Claude Code, custom clients)
|
|
- Initiates OAuth flow with PKCE (Proof Key for Code Exchange)
|
|
- Stores and sends access token with each request
|
|
- **Example**: Claude Desktop, Claude Code
|
|
|
|
### 2. MCP Server (Resource Server)
|
|
- **Role**: OAuth 2.0 Resource Server
|
|
- **Location**: This Nextcloud MCP Server implementation
|
|
- **Responsibilities**:
|
|
- Validates Bearer tokens by calling Nextcloud's userinfo endpoint
|
|
- Caches validated tokens (default: 1 hour TTL)
|
|
- Creates authenticated Nextcloud client instances per-user
|
|
- Enforces PKCE requirements (S256 code challenge method)
|
|
- Exposes Nextcloud functionality via MCP tools
|
|
|
|
**Key Files**:
|
|
- [`app.py`](../nextcloud_mcp_server/app.py) - OAuth mode detection and configuration
|
|
- [`auth/token_verifier.py`](../nextcloud_mcp_server/auth/token_verifier.py) - Token validation logic
|
|
- [`auth/context_helper.py`](../nextcloud_mcp_server/auth/context_helper.py) - Per-user client creation
|
|
|
|
### 3. Nextcloud OIDC Apps
|
|
|
|
#### a) `oidc` - OIDC Identity Provider
|
|
- **Role**: OAuth 2.0 Authorization Server
|
|
- **Location**: Nextcloud app (`apps/oidc`)
|
|
- **Endpoints**:
|
|
- `/.well-known/openid-configuration` - Discovery endpoint
|
|
- `/apps/oidc/authorize` - Authorization endpoint
|
|
- `/apps/oidc/token` - Token endpoint
|
|
- `/apps/oidc/userinfo` - User info endpoint (token validation)
|
|
- `/apps/oidc/jwks` - JSON Web Key Set
|
|
- `/apps/oidc/register` - Dynamic client registration
|
|
|
|
**Configuration**:
|
|
```bash
|
|
# Enable dynamic client registration (optional)
|
|
# Settings → OIDC → "Allow dynamic client registration"
|
|
```
|
|
|
|
#### b) `user_oidc` - OpenID Connect User Backend
|
|
- **Role**: Bearer token validation middleware
|
|
- **Location**: Nextcloud app (`apps/user_oidc`)
|
|
- **Responsibilities**:
|
|
- Validates Bearer tokens for Nextcloud API requests
|
|
- Creates user sessions from valid Bearer tokens
|
|
- Integrates with Nextcloud's authentication system
|
|
|
|
**Configuration**:
|
|
```bash
|
|
# Enable Bearer token validation (required)
|
|
php occ config:system:set user_oidc oidc_provider_bearer_validation --value=true --type=boolean
|
|
```
|
|
|
|
> [!IMPORTANT]
|
|
> The `user_oidc` app requires a patch to properly support Bearer token authentication for non-OCS endpoints. See [Upstream Status](oauth-upstream-status.md) for details.
|
|
|
|
### 4. Nextcloud Instance
|
|
- **Role**: Resource Owner / API Provider
|
|
- **Provides**: Notes, Calendar, Contacts, Deck, Files, etc.
|
|
|
|
## Authentication Flow
|
|
|
|
### Phase 1: OAuth Authorization (Steps 1-7)
|
|
|
|
1. **Client Connects**: MCP client connects to MCP server
|
|
2. **Auth Settings**: MCP server returns OAuth settings:
|
|
```json
|
|
{
|
|
"issuer_url": "https://nextcloud.example.com",
|
|
"resource_server_url": "http://localhost:8000",
|
|
"required_scopes": ["openid", "profile"]
|
|
}
|
|
```
|
|
3. **OAuth Flow**: Client initiates OAuth flow with PKCE
|
|
- Generates `code_verifier` (random string)
|
|
- Calculates `code_challenge` = SHA256(code_verifier)
|
|
- Redirects user to `/apps/oidc/authorize` with `code_challenge`
|
|
4. **User Authentication**: User logs in to Nextcloud via browser
|
|
5. **Authorization Code**: Nextcloud redirects back with authorization code
|
|
6. **Token Exchange**: Client exchanges code for access token
|
|
- Sends `code` + `code_verifier` to `/apps/oidc/token`
|
|
- OIDC app validates PKCE challenge
|
|
7. **Access Token**: Client receives access token (JWT or opaque)
|
|
|
|
### Phase 2: API Access (Steps 8-13)
|
|
|
|
8. **API Request**: Client sends MCP request with Bearer token
|
|
9. **Token Validation**: MCP server validates token:
|
|
- Checks cache (1-hour TTL by default)
|
|
- If not cached, calls `/apps/oidc/userinfo` with Bearer token
|
|
- Extracts username from `sub` or `preferred_username` claim
|
|
10. **User Info**: Nextcloud returns user info if token is valid
|
|
11. **Nextcloud API Call**: MCP server calls Nextcloud API on behalf of user
|
|
- Creates `NextcloudClient` instance with Bearer token
|
|
- User-specific permissions apply
|
|
12. **API Response**: Nextcloud returns data
|
|
13. **MCP Response**: MCP server returns formatted response to client
|
|
|
|
## Token Validation
|
|
|
|
The MCP server validates tokens using the **userinfo endpoint approach**:
|
|
|
|
### Why Userinfo (vs JWT Validation)?
|
|
|
|
**Advantages**:
|
|
- Works with both JWT and opaque tokens
|
|
- No need to manage JWKS rotation
|
|
- Always up-to-date (respects token revocation)
|
|
- Simpler implementation
|
|
|
|
**Caching Strategy**:
|
|
- Validated tokens cached for 1 hour (configurable)
|
|
- Cache keyed by token string
|
|
- Expired tokens re-validated automatically
|
|
|
|
**Implementation**: See [`NextcloudTokenVerifier`](../nextcloud_mcp_server/auth/token_verifier.py)
|
|
|
|
## PKCE Requirement
|
|
|
|
The MCP server **requires** PKCE with S256 code challenge method:
|
|
|
|
1. Server validates OIDC discovery advertises PKCE support
|
|
2. Checks for `code_challenge_methods_supported` field
|
|
3. Verifies `S256` is included in supported methods
|
|
4. Logs error if PKCE not properly advertised
|
|
|
|
**Why PKCE?**:
|
|
- Required by MCP specification
|
|
- Protects against authorization code interception
|
|
- Essential for public clients (desktop apps, CLI tools)
|
|
|
|
**Implementation**: See [`validate_pkce_support()`](../nextcloud_mcp_server/app.py#L31-L93)
|
|
|
|
## Client Registration
|
|
|
|
The MCP server supports two client registration modes:
|
|
|
|
### Automatic Registration (Dynamic Client Registration)
|
|
|
|
```bash
|
|
# No client credentials needed
|
|
NEXTCLOUD_HOST=https://nextcloud.example.com
|
|
```
|
|
|
|
**How it works**:
|
|
1. Server checks `/.well-known/openid-configuration` for `registration_endpoint`
|
|
2. Calls `/apps/oidc/register` to register a client on first startup
|
|
3. Saves credentials to `.nextcloud_oauth_client.json`
|
|
4. Reuses these credentials on subsequent startups
|
|
5. Re-registers only if credentials are missing or expired
|
|
|
|
**Best for**: Development, testing, quick deployments
|
|
|
|
### Pre-configured Client
|
|
|
|
```bash
|
|
# Manual client registration via CLI
|
|
php occ oidc:create --name="MCP Server" --type=confidential --redirect-uri="http://localhost:8000/oauth/callback"
|
|
|
|
# Configure MCP server
|
|
NEXTCLOUD_HOST=https://nextcloud.example.com
|
|
NEXTCLOUD_OIDC_CLIENT_ID=abc123
|
|
NEXTCLOUD_OIDC_CLIENT_SECRET=xyz789
|
|
```
|
|
|
|
**Best for**: Production, long-running deployments
|
|
|
|
## Per-User Client Instances
|
|
|
|
Each authenticated user gets their own `NextcloudClient` instance:
|
|
|
|
```python
|
|
# From MCP context (contains validated token)
|
|
client = get_client_from_context(ctx)
|
|
|
|
# Creates NextcloudClient with:
|
|
# - username: from token's 'sub' or 'preferred_username' claim
|
|
# - auth: BearerAuth(token)
|
|
```
|
|
|
|
**Benefits**:
|
|
- User-specific permissions
|
|
- Audit trail (actions appear from correct user)
|
|
- No shared credentials
|
|
- Multi-user support
|
|
|
|
**Implementation**: See [`get_client_from_context()`](../nextcloud_mcp_server/auth/context_helper.py)
|
|
|
|
## Security Considerations
|
|
|
|
### Token Storage
|
|
- MCP client stores access token
|
|
- MCP server does NOT store tokens (validates per-request)
|
|
- Token validation results cached in-memory only
|
|
|
|
### PKCE Protection
|
|
- Server validates PKCE is advertised
|
|
- Client MUST use PKCE with S256
|
|
- Protects against authorization code interception
|
|
|
|
### Scopes
|
|
- Required scopes: `openid`, `profile`
|
|
- Additional scopes inferred from userinfo response
|
|
|
|
### Token Validation
|
|
- Every MCP request validates Bearer token
|
|
- Cached for performance (1-hour default)
|
|
- Calls userinfo endpoint for validation
|
|
|
|
## Configuration
|
|
|
|
See [Configuration Guide](configuration.md) for all OAuth environment variables:
|
|
|
|
| Variable | Purpose |
|
|
|----------|---------|
|
|
| `NEXTCLOUD_HOST` | Nextcloud instance URL |
|
|
| `NEXTCLOUD_OIDC_CLIENT_ID` | Pre-configured client ID (optional) |
|
|
| `NEXTCLOUD_OIDC_CLIENT_SECRET` | Pre-configured client secret (optional) |
|
|
| `NEXTCLOUD_MCP_SERVER_URL` | MCP server URL for OAuth callbacks |
|
|
| `NEXTCLOUD_OIDC_CLIENT_STORAGE` | Path for auto-registered credentials |
|
|
|
|
## Testing
|
|
|
|
The integration test suite includes comprehensive OAuth testing:
|
|
|
|
- **Automated tests** (Playwright): [`tests/client/test_oauth_playwright.py`](../tests/client/test_oauth_playwright.py)
|
|
- **Fixtures**: [`tests/conftest.py`](../tests/conftest.py)
|
|
|
|
Run OAuth tests:
|
|
```bash
|
|
# Start OAuth-enabled MCP server
|
|
docker-compose up --build -d mcp-oauth
|
|
|
|
# Run automated tests
|
|
uv run pytest tests/client/test_oauth_playwright.py --browser firefox -v
|
|
```
|
|
|
|
## See Also
|
|
|
|
- [OAuth Setup Guide](oauth-setup.md) - Configuration steps
|
|
- [OAuth Quick Start](quickstart-oauth.md) - Get started quickly
|
|
- [Upstream Status](oauth-upstream-status.md) - Required upstream patches
|
|
- [OAuth Troubleshooting](oauth-troubleshooting.md) - Common issues
|
|
- [RFC 6749](https://www.rfc-editor.org/rfc/rfc6749) - OAuth 2.0 Authorization Framework
|
|
- [RFC 7636](https://www.rfc-editor.org/rfc/rfc7636) - PKCE
|
|
- [OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html)
|