Skip to content

feat: OAuth Refresh Token Grant with configurable TTLs #46

@danieljustus

Description

@danieljustus

Ziel

MCP-Clients können via refresh_token-Grant ihre Tokens automatisch erneuern, ohne dass der User erneut den Authorization-Code-Flow durchlaufen muss. Die Token-TTLs sind via Konfiguration einstellbar.

Warum jetzt

Der Token-Endpoint unterstützt aktuell nur grant_type=authorization_code. opencode muss nach 24h den kompletten Flow wiederholen, weil kein Refresh-Token-Grant (RFC 6749 §6) implementiert ist. Refresh-Tokens machen die persistente Client-Registry (separates Issue) erst richtig wertvoll: Clients rotieren selbstständig und bleiben über Wochen verbunden.

Aktueller Stand / Evidenz

  • internal/mcp/serverbootstrap/oauth.go:234grant_type != "authorization_code" → einziger unterstützter Grant
  • internal/mcp/serverbootstrap/oauth.go:25824*time.Hour hardcodiert
  • internal/mcp/serverbootstrap/wellknown.go:49grant_types_supported: ["authorization_code"]refresh_token fehlt
  • internal/mcp/token.goScopedToken hat kein RefreshTokenHash-Feld
  • internal/config/schema.go:52-71MCPConfig hat keine OAuthConfig-Felder für TTLs

In Scope

  • ScopedToken um RefreshTokenHash string + RefreshExpiresAt *time.Time erweitern
  • TokenRegistry.CreateWithRefresh(label, allowed, agent, accessTTL, refreshTTL) — erzeugt access+refresh-Token-Paar mit separaten Hashes
  • TokenRegistry.RotateViaRefreshToken(rawRefreshToken) — revoziert altes access-Token, erzeugt neues Paar, constant-time-compare der Hashes
  • TokenRegistryFile.Version von 1 auf 2 erhöhen (alte Dateien load-kompatibel)
  • handleOAuthToken um grant_type=refresh_token-Switch erweitern
  • OAuthConfig in MCPConfig: AccessTokenTTL (default 24h), RefreshTokenTTL (default 720h/30d)
  • fileMCPConfig-Pointer-Felder + MergeFileMCPConfig-Roundtrip + defaultMCPConfig()
  • wellknown.go:49grant_types_supported um "refresh_token" erweitern
  • handleOAuthRegister-Response: grant_types auf ["authorization_code", "refresh_token"] aktualisieren
  • Abgelaufenes refresh_token → RFC-konformes invalid_grant

Out of Scope

  • Persistente Client-Registry (separates Issue)
  • OAuth-Code-Store-Persistenz (5-min-TTL, Edge-Case)
  • Migration auf SQLite
  • Andere MCP-Clients automatisiert testen

Akzeptanzkriterien

  • oauth_refresh_test.go: Full-Flow → refresh_token-Grant → altes access-Token revoked → neues Paar funktioniert
  • oauth_refresh_test.go: abgelaufenes refresh_token → invalid_grant
  • oauth_config_test.go: custom TTLs in Config → handleOAuthToken respektiert sie (z. B. Access 10s, Refresh 30s → nach 15s kein Refresh mehr möglich)
  • /.well-known/oauth-authorization-server listet grant_types_supported: ["authorization_code", "refresh_token"]
  • POST /oauth/register gibt grant_types: ["authorization_code", "refresh_token"] zurück
  • mcp-tokens.json zeigt nach CreateWithRefresh sowohl access- als auch refresh-token-Einträge
  • Config mit oauth.access_token_ttl: 10s + oauth.refresh_token_ttl: 30s wird korrekt eingelesen
  • Lint und go test sind grün

Risiken / Abhängigkeiten

  • Baut auf dem AtomicWrite-Pattern in token.go auf
  • Baut auf Issue "Persistent OAuth Client Registry" auf (getrennte Issues, aber logisch verwandt)
  • Schema-Version 1→2 erfordert vorsichtige Migration (leere Refresh-Felder ignorieren)
  • Refresh-Token-Rotation folgt dem "Single-Use"-Pattern: Client bekommt bei Rotation immer ein neues refresh_token, das alte wird invalid

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions