No description
  • Go 92.3%
  • Shell 6.7%
  • Dockerfile 1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
jochen 96fc79c4a3
Some checks failed
ci / test (push) Has been cancelled
Merge pull request 'Initiale Version: MCP-Auth-Sidecar (RFC 9728 + JWT-prüfender Proxy)' (#1) from feature/initial-sidecar into main
Reviewed-on: #1
Reviewed-by: jochen <jochen@bocc.de>
2026-09-26 19:20:16 +00:00
.forgejo/workflows Integrationstests, Containerfile, Quadlet, Keycloak-Setup, CI, README 2026-09-25 17:25:10 +00:00
deploy Keycloak-Setup-Skript und README 2026-09-25 17:26:11 +00:00
internal Integrationstests, Containerfile, Quadlet, Keycloak-Setup, CI, README 2026-09-25 17:25:10 +00:00
.containerignore Integrationstests, Containerfile, Quadlet, Keycloak-Setup, CI, README 2026-09-25 17:25:10 +00:00
.gitignore Initial commit 2026-09-25 17:19:20 +00:00
Containerfile Integrationstests, Containerfile, Quadlet, Keycloak-Setup, CI, README 2026-09-25 17:25:10 +00:00
go.mod Sidecar für MCP-OAuth: RFC-9728-Metadaten, AS-Metadaten-Spiegel, JWT-prüfender Reverse Proxy 2026-09-25 17:22:26 +00:00
main.go Sidecar für MCP-OAuth: RFC-9728-Metadaten, AS-Metadaten-Spiegel, JWT-prüfender Reverse Proxy 2026-09-25 17:22:26 +00:00
README.md Keycloak-Setup-Skript und README 2026-09-25 17:26:11 +00:00

mcp-auth-sidecar

Kleiner Sidecar, der einen bestehenden MCP-Server bzw. ein MCP-Gateway für die MCP-Autorisierung (OAuth 2.1) fit macht, ohne einen vollwertigen Identity-Aware-Proxy wie Pomerium. Als Authorization Server dient Keycloak, davor kann eine WAF wie FortiWeb stehen.

Ein Go-Binary ohne Fremdabhängigkeiten (nur Standardbibliothek), Container ca. 10 MB, keine Datenbank.

Was er liefert

Endpunkt Zweck
/.well-known/oauth-protected-resource und /.well-known/oauth-protected-resource/<pfad> Protected Resource Metadata (RFC 9728), zeigt auf Keycloak als Authorization Server
/.well-known/oauth-authorization-server, …/oauth-authorization-server/<issuer-pfad>, …/openid-configuration/<issuer-pfad> Spiegel der Keycloak-Discovery (RFC 8414), gecacht. Hilft älteren MCP-Clients und Clients, die „path insertion“ nutzen
alle übrigen Pfade (Proxy-Modus) Bearer-Token prüfen, bei Erfolg an das Upstream weiterreichen, sonst spec-konform 401 mit WWW-Authenticate: Bearer resource_metadata="…"
/-/healthz, /-/readyz Liveness bzw. Readiness (bereit, sobald JWKS geladen)

Ablauf

sequenceDiagram
    participant C as MCP-Client (z. B. Claude)
    participant F as FortiWeb
    participant S as mcp-auth-sidecar
    participant G as MCP-Gateway
    participant K as Keycloak
    C->>F: POST /mcp (ohne Token)
    F->>S: weiter
    S-->>C: 401 WWW-Authenticate: resource_metadata=…
    C->>S: GET /.well-known/oauth-protected-resource/mcp
    S-->>C: authorization_servers: [Keycloak-Realm]
    C->>K: Discovery, Dynamic Client Registration, Login (PKCE)
    K-->>C: Access Token (aud = MCP-Ressource)
    C->>F: POST /mcp, Authorization: Bearer …
    F->>S: weiter
    S->>S: Signatur (JWKS), iss, aud, exp, typ, Scopes prüfen
    S->>G: weiter mit X-Auth-Subject/Email/…
    G-->>C: Antwort / SSE-Stream (ungepuffert)

Betriebsarten

proxy (Standard): Der Sidecar sitzt vor dem Gateway und prüft selbst. Er streamt SSE und Streamable HTTP ohne Pufferung und reicht Mcp-Session-Id durch. Eingehende Header mit dem Identitäts-Präfix (X-Auth-) werden immer entfernt, damit sie nicht gefälscht werden können.

metadata: Nur die /.well-known/-Endpunkte. Die WAF routet diese Pfade zum Sidecar und prüft Tokens selbst. Das funktioniert nur, wenn die WAF bei fehlendem oder ungültigem Token ein echtes 401 mit korrektem WWW-Authenticate-Header liefert. Sonst startet der Client den OAuth-Flow nie.

Konfiguration

Alle Variablen haben das Präfix MCPAUTH_. Vorlage: deploy/quadlet/sidecar.env.example.

Variable Standard Beschreibung
RESOURCE_URL – (Pflicht) Öffentliche URL des MCP-Endpunkts, z. B. https://mcp.example.de/mcp
ISSUER_URL – (Pflicht) Keycloak-Realm, z. B. https://kc.example.de/realms/mcp
UPSTREAM_URL – (Pflicht im Proxy-Modus) MCP-Gateway, z. B. http://mcp-gateway:3000
MODE proxy proxy oder metadata
LISTEN_ADDR :8080 Listen-Adresse
AUDIENCE RESOURCE_URL Erlaubte aud-Werte (Komma/Leerzeichen-getrennt)
REQUIRED_SCOPES – Pflicht-Scopes, sonst 403 insufficient_scope
SCOPES_SUPPORTED REQUIRED_SCOPES Für die Metadaten
RESOURCE_NAME, RESOURCE_DOCUMENTATION – Optionale Metadatenfelder
JWKS_URL aus Discovery Nur setzen, wenn abweichend
ALLOWED_ALGS RS*, PS*, ES*, EdDSA Nur asymmetrische Verfahren möglich
CLOCK_SKEW 60s Toleranz für exp/nbf/iat
JWKS_REFRESH_INTERVAL 15m Periodisches Neuladen
JWKS_MIN_REFRESH_INTERVAL 30s Rate-Limit fürs Nachladen bei unbekannter kid (Schlüsselrotation)
DISCOVERY_CACHE_TTL 10m Cache für Keycloak-Discovery
HTTP_TIMEOUT 10s Timeout für Keycloak-Abrufe
SERVE_AS_METADATA true AS-Metadaten spiegeln
FORWARD_IDENTITY true X-Auth-Subject, -Email, -Username, -Client-Id, -Scopes setzen
HEADER_PREFIX X-Auth- Präfix der Identitäts-Header
STRIP_AUTHORIZATION false Token nicht ans Upstream weitergeben
PRESERVE_HOST false Original-Host ans Upstream
TRUST_FORWARDED_HEADERS false X-Forwarded-* vom vorgelagerten Proxy übernehmen (hinter FortiWeb: true)
ALLOW_PREFLIGHT true CORS-Preflights ohne Token durchlassen
REQUIRE_BEARER_TYP true Keycloak-Claim typ muss Bearer sein (blockt ID-/Refresh-Tokens)
ALLOW_INSECURE_HTTP false http:// für Resource/Issuer erlauben (nur Tests)
LOG_LEVEL, LOG_FORMAT info, json Logging

Keycloak

deploy/keycloak/setup-realm.sh richtet einen eigenen Realm per kcadm.sh ein. Das Skript ist erneut ausführbar.

  • Client Scope mcp mit Audience-Mapper auf MCP_RESOURCE_URL, als Realm-Default für neue Clients.
  • Anonyme Dynamic Client Registration nur für Redirect-URIs auf vertrauenswürdigen Hosts (Standard claude.ai, Callback https://claude.ai/api/mcp/auth_callback). Die übrigen Keycloak-Default-Policies (Consent Required, Max Clients, Full Scope Disabled) bleiben aktiv.
  • PKCE S256 für alle Clients des Realms per Client Policy. Ersetzt vorhandene Client Policies des Realms.

Den eigentlichen Login (AD/LDAP, Entra ID, SAML-IdP) bindet man im Realm als User Federation oder Identity Provider an. Keycloak übernimmt dann nur den MCP-spezifischen OAuth-Teil.

FortiWeb davor

  • Server-Pool auf den Sidecar zeigen lassen. Die /.well-known/-Pfade müssen ohne Authentisierung erreichbar sein.
  • Für SSE: keine Response-Pufferung bzw. Body-Inspection auf text/event-stream, großzügige Idle-Timeouts.
  • Header Authorization, Mcp-Session-Id und WWW-Authenticate unverändert durchreichen.
  • Optional für Claude-Connectoren: IP-Allowlist auf die veröffentlichten Egress-IPs von Anthropic.

Deployment mit Podman/Quadlet

podman build -t git.bocc.de/claude/mcp-auth-sidecar:0.1.0 --build-arg VERSION=0.1.0 .
podman push git.bocc.de/claude/mcp-auth-sidecar:0.1.0

sudo install -d -m 700 /etc/mcp-auth-sidecar
sudo install -m 600 deploy/quadlet/sidecar.env.example /etc/mcp-auth-sidecar/sidecar.env   # anpassen
sudo cp deploy/quadlet/mcp.network deploy/quadlet/mcp-auth-sidecar.container /etc/containers/systemd/
sudo systemctl daemon-reload && sudo systemctl start mcp-auth-sidecar

Das bestehende Gateway kommt mit Network=mcp.network ins selbe Netz und veröffentlicht selbst keinen Port mehr. Es ist dann nur noch über den Sidecar erreichbar.

Entwicklung

go vet ./...
go test -race ./...
go build -o mcp-auth-sidecar .

Tests decken ab: Signaturprüfung (RS256, PS256, ES256, EdDSA), Ablehnung von alg=none, manipulierten Payloads, falschen iss/aud, abgelaufenen Tokens und ID-Tokens, schwachen oder ungültigen JWKs, Schlüsselrotation mit Rate-Limit, 401/403-Challenges, Header-Spoofing, CORS-Preflight und ungepuffertes SSE.

Sicherheitshinweise

  • Keine Token-Durchreichung an Dritt-APIs: Das Gateway ist selbst die Ressource. Braucht es weitere APIs, holt es eigene Tokens (z. B. per Token Exchange).
  • Tokens werden nie geloggt. Abgelehnte Tokens werden mit Grund, Remote-Adresse und Pfad auf info protokolliert.
  • Upstream-Port nicht direkt veröffentlichen, sonst lässt sich der Sidecar umgehen.

Offen / zu prüfen

  • setup-realm.sh gegen die eingesetzte Keycloak-Version testen. Admin-API-Details, z. B. der Client-Policy-Condition any-client, können sich zwischen Versionen unterscheiden.
  • Basis-Images im Containerfile per Digest pinnen.
  • Runner-Label in .forgejo/workflows/ci.yml an die eigene Runner-Konfiguration anpassen.
  • Neuere MCP-Spezifikationen erlauben statt DCR auch Client-ID-Metadata-Dokumente. Ob Keycloak und die genutzten Clients das bereits unterstützen, ist zu prüfen. Am Sidecar ändert das nichts.