Files
nextcloud-mcp-server/docs/oauth-architecture.md
T

20 KiB

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:

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:

# 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:

# 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 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:
    {
      "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)

  1. API Request: Client sends MCP request with Bearer token
  2. 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
  3. User Info: Nextcloud returns user info if token is valid
  4. Nextcloud API Call: MCP server calls Nextcloud API on behalf of user
    • Creates NextcloudClient instance with Bearer token
    • User-specific permissions apply
  5. API Response: Nextcloud returns data
  6. 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

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()

Client Registration

The MCP server supports two client registration modes:

Automatic Registration (Dynamic Client Registration)

# 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

# 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:

# 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()

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

  • Base required scopes: openid, profile, email
  • App-specific scopes control access to individual Nextcloud apps
  • See OAuth Scopes section for complete scope reference

Token Validation

  • Every MCP request validates Bearer token
  • Cached for performance (1-hour default)
  • Calls userinfo endpoint for validation

OAuth Scopes

The Nextcloud MCP Server implements fine-grained OAuth scopes for each Nextcloud app integration. Scopes control which tools are visible and accessible to users based on their granted permissions.

Scope-Based Access Control

When using OAuth authentication:

  1. Dynamic Discovery: The server automatically discovers all required scopes from @require_scopes decorators on MCP tools
  2. Tool Filtering: Tools are dynamically filtered based on the user's token scopes - users only see tools they have permission to use
  3. Per-Tool Enforcement: Each tool validates required scopes before execution, returning a 403 error if insufficient scopes are present

Supported Scopes

The server supports the following OAuth scopes, organized by Nextcloud app:

Base OIDC Scopes

  • openid - OpenID Connect authentication (required)
  • profile - Access to user profile information (required)
  • email - Access to user email address (required)

Notes App

  • notes:read - Read notes, search notes, get note attachments
  • notes:write - Create, update, append to, and delete notes

Calendar App

  • calendar:read - List calendars, read events, search events
  • calendar:write - Create, update, and delete calendars and events

Calendar Tasks (VTODO)

  • todo:read - List and read CalDAV tasks
  • todo:write - Create, update, and delete CalDAV tasks

Contacts App

  • contacts:read - List address books and read contacts (CardDAV)
  • contacts:write - Create, update, and delete address books and contacts

Cookbook App

  • cookbook:read - Read recipes, search recipes
  • cookbook:write - Create, update, and delete recipes

Deck App

  • deck:read - List boards, stacks, cards, and labels
  • deck:write - Create, update, and delete boards, stacks, cards, and labels

Tables App

  • tables:read - List tables and read rows
  • tables:write - Create, update, and delete rows in tables

Files (WebDAV)

  • files:read - List files, read file contents, search files
  • files:write - Upload, update, move, copy, and delete files

Sharing

  • sharing:read - List shares and read share information
  • sharing:write - Create, update, and delete shares

Scope Discovery

The MCP server provides scope discovery through two mechanisms:

1. Protected Resource Metadata (PRM) Endpoint

# Query the PRM endpoint
curl http://localhost:8000/.well-known/oauth-protected-resource/mcp

# Response includes dynamically discovered scopes
{
  "resource": "http://localhost:8000/mcp",
  "scopes_supported": ["openid", "profile", "email", "notes:read", ...],
  "authorization_servers": ["https://nextcloud.example.com"],
  "bearer_methods_supported": ["header"],
  "resource_signing_alg_values_supported": ["RS256"]
}

The scopes_supported field is dynamically generated from all registered MCP tools, ensuring it always reflects the actual available scopes.

2. Scope Enforcement via Decorators

Tools are decorated with @require_scopes() to declare their required permissions:

from nextcloud_mcp_server.auth import require_scopes

@mcp.tool()
@require_scopes("notes:read")
async def nc_notes_get_note(ctx: Context, note_id: int):
    """Get a specific note by ID"""
    # Implementation

Client Registration Scopes

During OAuth client registration (dynamic or manual), clients request a set of scopes that define the maximum allowed scopes for that client. The actual per-tool enforcement is handled separately via decorators.

Environment Variable:

NEXTCLOUD_OIDC_SCOPES="openid profile email notes:read notes:write calendar:read calendar:write ..."

Default: All supported scopes (recommended for development)

Note

: Client registration scopes define the maximum permissions. The MCP server's PRM endpoint dynamically advertises the actual supported scopes based on registered tools.

Step-Up Authorization

The server supports OAuth step-up authorization (RFC 8693). If a user attempts to use a tool requiring scopes they don't have:

  1. Tool returns 403 Forbidden with InsufficientScopeError
  2. Response includes WWW-Authenticate header listing missing scopes:
    WWW-Authenticate: Bearer error="insufficient_scope", scope="notes:write", resource_metadata="..."
    
  3. Client can re-authorize with additional scopes

Scope Validation

All scope enforcement happens at two levels:

  1. Tool Visibility: During list_tools requests, only tools matching the user's token scopes are returned
  2. Execution Time: When calling a tool, the @require_scopes decorator validates the token has necessary scopes

Example:

# User token has: ["openid", "profile", "email", "notes:read"]
# They will see: 4 read-only notes tools
# They will NOT see: 3 write notes tools (notes:write required)
# Attempting to call a write tool returns 403 Forbidden

Configuration

See Configuration Guide 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:

Run OAuth tests:

# 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