MCP-Server mit OAuth 2.1 absichern, in 60 Minuten produktiv
Wann brauchst Du Auth für Deinen MCP-Server, wann nicht. PKCE-Pflicht, Discovery via .well-known, Resource Indicators (RFC 8707). Drei Setup-Optionen: WorkOS AuthKit, Auth0 für MCP, eigener Authorization Server.
Du hast einen MCP-Server gebaut, der lief erst mal lokal als stdio. Jetzt willst Du ihn als Streamable-HTTP-Server hosten damit andere Leute oder Cursor/Claude.ai/VSCode-Plugins drauf zugreifen können. Spätestens da brauchst Du Auth, sonst kann jeder mit einer URL Deinen Server lesen, schreiben oder Tools aufrufen.
Die MCP-Spec verlangt OAuth 2.1 mit PKCE als Auth-Standard. Das ist nicht optional. Hier ist wie das in der Praxis funktioniert und welche der drei Setup-Optionen für Deine Situation passt.
1. Wann Du Auth brauchst und wann nicht
Erstmal die Klarstellung. Lokale MCP-Server die per stdio laufen brauchen kein OAuth. Die MCP-Spec sagt explizit: Stdio-Implementierungen sollten Credentials aus der Environment lesen, nicht über OAuth-Flows gehen. Du gibst API-Keys über .env oder den env-Block in der MCP-Config rein, fertig.
OAuth wird Pflicht sobald Dein Server über HTTP exposed ist. Streamable HTTP, Server-Sent Events, klassisches Request-Response. Sobald Daten über das Netz gehen und es um geschützte Ressourcen geht, brauchst Du Auth.
Dritter Fall: read-only-Demo-Server ohne sensitive Daten. Theoretisch kannst Du ohne Auth fahren wenn Dein Server nur public Daten exposed (z.B. ein Wetter-MCP-Server). Praktisch macht das aber Probleme weil Claude.ai und andere Clients den Auth-Handshake erwarten und ohne Header verwirrt werden. Auch für Public-Demos lohnt der minimale Setup.
2. Was die Spec verlangt
Die MCP-Spec 2025-06-18 verlangt fuenf Dinge wenn Du OAuth implementierst.
Erstens: OAuth 2.1, nicht OAuth 2.0. Der Unterschied ist wesentlich. 2.1 verlangt PKCE für alle Flows (nicht nur public clients), droppt den implicit grant komplett, und verlangt exact-redirect-URI-matching. Wer 2.0 implementiert hat schon den Stand von vor 5 Jahren.
Zweitens: PKCE mit S256-Methode. Der Client erzeugt ein zufälliges code_verifier, hasht das als code_challenge mit SHA256, schickt das in der Authorization-Request, und beim Token-Exchange schickt er das ursprüngliche code_verifier mit. Ohne das geht der MCP-Client gar nicht erst los.
Drittens: Discovery via .well-known/oauth-protected-resource (RFC 9728). Wenn ein Client zum ersten Mal auf Deinen MCP-Server zugreift, schickt er einen Request ohne Token. Du antwortest mit HTTP 401 und einem WWW-Authenticate Header der auf /.well-known/oauth-protected-resource zeigt. Da liegt ein JSON-Dokument das auf den Authorization-Server zeigt.
Viertens: Resource Indicators (RFC 8707). Der MCP-Client muss in der Authorization- und Token-Request einen resource Parameter mitschicken der die kanonische URI Deines MCP-Servers angibt. Beispiel: &resource=https%3A%2F%2Fmcp.studiomeyer.io. Damit wird das Token an Dein Server gebunden, ein gestohlenes Token kann nicht für einen anderen MCP-Server missbraucht werden.
Fünftens: Token-Audience-Validation. Wenn ein Request mit Token reinkommt, musst Du serverseitig prüfen dass das Token tatsächlich für Deine kanonische URI ausgestellt wurde, nicht für einen anderen Server. Das verhindert die "confused deputy" Schwachstelle.
3. Option A, WorkOS AuthKit
Wenn Du kein eigenes Auth-System hast und schnell live gehen willst, ist WorkOS aktuell die einfachste Lösung. AuthKit handled die komplette OAuth-Authorization-Server-Seite für Dich. Login-Page, User-Management, Token-Issuance, PKCE, Discovery-Endpoints, alles fertig.
Setup grob in 4 Schritten:
- WorkOS-Account anlegen (workos.com), neues Projekt für Deinen MCP-Server, AuthKit-Domain konfigurieren.
- In Deinem MCP-Server einen
/.well-known/oauth-protected-resourceEndpoint setzen, der aufhttps://your-domain.authkit.app/.well-known/oauth-authorization-serverzeigt. - Im Server jeden Request validieren:
Authorization: Bearer ...Token einlesen, gegen den WorkOS JWKS-Endpoint validieren (Standard JWT-Verify-Library), Audience-Claim auf Deinen MCP-Server-URI checken, sonst 401. - Login-URL in Deinem Client (Claude.ai etc.) zeigt auf die WorkOS Universal-Login-Page, von dort kommt der User mit Token zurück.
Pricing aktuell ist Free für bis zu 1 Million MAU mit Standard-Features, danach Enterprise-Preise. Für 99 Prozent der MCP-Server kostet das nichts.
Vorteil: minimales eigenes Code, alles enterprise-ready (SAML, SCIM falls Du das später brauchst). Nachteil: Vendor-Lock-in. Du bist abhaengig von WorkOS-Verfügbarkeit. Für Production-MCP-Server in 2026 ist das aktuell die Defacto-Empfehlung der MCP-Community.
4. Option B, Auth0 für MCP
Auth0 hat seit Mai 2026 ein dediziertes "Auth for MCP" Produkt. Wenn Du sowieso schon Auth0 für Deine App nutzt, ist das die natürliche Wahl. Du kriegst MCP-spezifische Features wie CIMD-Registration und On-Behalf-Of-Token-Exchange für AI-Agents oben drauf.
Setup-Pattern ähnlich wie WorkOS:
- Auth0-Tenant einrichten, neue Application vom Typ "Machine to Machine" oder "Native" je nach Client.
- MCP-Spezifika konfigurieren: Universal Login als Authorization-Server, PKCE-Pflicht, Resource-Indicators-Support aktivieren.
- Im MCP-Server JWT-Validation gegen Auth0 JWKS-Endpoint, Audience auf Deine MCP-URL.
- Discovery-Endpoints in Auth0 verfügbar machen, lokalen
/.well-known/oauth-protected-resourceEndpoint dazukleben.
Vorteil gegenüber WorkOS: tiefere Enterprise-Features (Anomaly-Detection, Risk-Scoring), größerer Skill-Pool an Devs die Auth0 schon kennen, bessere SAML-Integration falls Du in Enterprise verkaufst. Nachteil: größeres Pricing-Stufen, etwas komplexere Konfiguration. Auth0 wurde 2021 von Okta gekauft, was Pricing-Modell und Velocity-Geschwindigkeit beeinflusst hat.
Wenn Du noch keine Identity-Lösung hast, ist WorkOS schneller live. Wenn Du schon Auth0 nutzt, geht "Auth for MCP" reibungsloser.
5. Option C, Cloudflare Workers OAuth Library
Für Self-Hosters die keinen externen Provider wollen gibt es die Cloudflare-OAuth-Library. Implementiert die komplette Provider-Seite (DCR, PKCE, Discovery) als Cloudflare Worker. Standalone oder kombiniert mit Cloudflare Access (deren SSO-Produkt).
Das ist die Variante die viele aktuelle Production-MCP-Server intern nutzen, oft als Layer unter einem Provider wie WorkOS oder Auth0. Stand Mai 2026 ist die Library Open-Source, in TypeScript geschrieben, läuft komplett in der Worker-Umgebung.
Setup-Aufwand größer als A oder B, aber Du hast volle Kontrolle. Prüfe vorher: hostest Du eh schon auf Cloudflare? Hast Du Bandwidth für Auth-Operations als zusätzliche Maintenance? Wenn beide Antworten Ja sind, ist diese Option langfristig billiger und unabhaengiger.
Plus: weil das eine Open-Source-Library ist, kannst Du den Auth-Layer auf Mehrere MCP-Server gleichzeitig anwenden ohne pro Server einen WorkOS oder Auth0 Account aufzusetzen.
6. Discovery-Setup, der entscheidende Schritt
Egal welche Option Du nimmst, der MCP-Client erwartet einen klaren Discovery-Pfad. Skizze:
1. Client schickt MCP-Request ohne Token an https://mcp.example.com/mcp
2. Server antwortet 401 mit:
WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource"
3. Client GETet https://mcp.example.com/.well-known/oauth-protected-resource
4. Server liefert JSON:
{
"resource": "https://mcp.example.com",
"authorization_servers": ["https://your-domain.authkit.app"]
}
5. Client GETet https://your-domain.authkit.app/.well-known/oauth-authorization-server
6. Authorization Server liefert Metadata mit Endpoints (authorize, token, jwks_uri).
Das ist die Spec-Pflicht. Wenn ein dieser fuenf Schritte fehlt, scheitert der Auto-Discovery-Flow und Dein Server ist von Claude.ai oder Cursor nicht nutzbar.
Den resource_metadata-Endpoint kannst Du auch statisch ausliefern. Einfaches HTML-File auf Deinem Webserver mit dem JSON-Body, Content-Type application/json, fertig.
7. Token-Validation im Server
Im MCP-Server musst Du jeden Request mit einem Token validieren. Pseudocode:
async function validateToken(req): Promise<TokenClaims> {
const auth = req.headers.get("authorization");
if (!auth || !auth.startsWith("Bearer ")) throw new Unauthorized();
const token = auth.slice(7);
const claims = await verifyJWT(token, JWKS_URI);
// Audience-Validation, RFC 8707
if (claims.aud !== "https://mcp.example.com") {
throw new Unauthorized("token audience mismatch");
}
// Expiry
if (claims.exp * 1000 < Date.now()) throw new Unauthorized("expired");
return claims;
}
Der aud Check ist kritisch. Ohne den könnten Tokens für andere Services missbraucht werden um Deinen MCP-Server anzusprechen. Das ist die häufigste Implementation-Luecke.
scope und sub (User-ID) liest Du aus dem Token-Claim wenn Du differenzierte Permissions oder Per-User-Logik brauchst. Standard MCP nutzt das nicht zwingend, aber Du wirst es brauchen sobald Du multi-tenant gehst.
8. Refresh-Token-Rotation, oft uebersehen
Public Clients (also Browser-basierte oder Mobile-Apps wie Claude.ai) müssen Refresh-Tokens rotieren. Heißt: jedes Mal wenn der Client ein Refresh-Token gegen einen Access-Token tauscht, gibt der Authorization-Server ein neues Refresh-Token zurück und das alte ist tot.
Wenn Du WorkOS oder Auth0 nutzt, machen die das automatisch. Wenn Du Self-Host gehst, musst Du das selbst implementieren. Nicht-rotierende Refresh-Tokens sind in OAuth 2.1 verboten weil ein gestohlenes Token sonst beliebig oft genutzt werden kann.
Prüfe Deinen Provider: bei Self-Host check die Library-Doku, bei Provider die Default-Settings. Bei einigen Providern muss Token-Rotation explizit aktiviert werden.
9. Die drei Stolperfallen
Fehlende Audience-Validation. Du baust Dir den OAuth-Flow, alles funktioniert, Tokens werden ausgegeben, Server akzeptiert sie. Was Du vergisst: prüfen ob der aud-Claim Deinen Server-URI ist. Folge: jemand bekommt ein Token von einem anderen Service, schickt es an Deinen MCP-Server, Du akzeptierst es. Das nennt die Spec "confused deputy". Pflicht-Fix: aud Validation immer.
Resource-Parameter wird vergessen. Der MCP-Client muss den resource-Parameter in jeder Authorization- und Token-Request mitschicken. Wenn der Client das nicht tut (oft bei selbstgebauten Clients), gibt der AuthServer Tokens ohne korrekten Audience-Claim aus. Workaround: dokumentier in Deiner Server-Doku dass MCP-Clients RFC 8707 Resource-Indicators implementieren müssen.
HTTPS-Pflicht ignoriert. Alle Authorization-Server-Endpunkte müssen über HTTPS laufen, Redirect-URIs müssen entweder localhost oder HTTPS sein. Wenn Du in Dev oft mit HTTP-Setups testet und Production dann HTTPS dazuschraubst, brichst Du subtil den Trust-Chain. Prüf in Dev mit lokalen Self-Signed-Certs oder ngrok statt mit HTTP.
10. Wie Du anfangen solltest
Wenn Du noch keinen MCP-Server in Production hast: bau ihn erst mal als stdio-Server, get-feature-complete, deploy lokal. Erst dann über HTTP-Hosting nachdenken und damit über OAuth.
Wenn Du HTTP-Hosting brauchst: pick WorkOS AuthKit, leg in 1 bis 2 Stunden den Auth-Layer drumrum, ship. Später optimieren wenn Lasten oder Sonderfeatures kommen.
Wenn Du in Enterprise verkaufst: pick Auth0 für SAML/SCIM-Integration. Mehr Setup-Aufwand, dafuer Enterprise-Compliance abgedeckt.
Wenn Du DIY und Cost-bewusst bist: Cloudflare Worker OAuth Library plus Cloudflare Access. 2 bis 4 Tage Setup, danach skalierst Du für fast nichts.
Schau Dir auch das Playbook mcp-server-publishen an wenn Du den Server fertig hast und auf MCP-Marktplaetzen distributieren willst, da geht es um Discovery-Listings die über den OAuth-Setup hinaus wichtig sind.
Source
Specs verifiziert via offizielle MCP-Dokumentation und Provider-Doku am 2026-05-07:
- modelcontextprotocol.io/specification/2025-06-18/basic/authorization (OAuth 2.1 Anforderungen, PKCE-Pflicht, Resource Indicators, Discovery, Token-Audience-Validation)
- workos.com/blog/best-mcp-server-authentication-providers (Provider-Vergleich Mai 2026, WorkOS AuthKit, Auth0 GA, Cloudflare-Library)
- prefect.io/resources/mcp-oauth (FastMCP-OAuth-Implementation, Token-Lifecycle)
Konsultierte Recipes in der Academy:
- Phase 6 MCP Authorization Patterns
- Recipe 6.4 Token Validation
- Lesson L4-06 MCP-Discovery und Marketplaces