← Level 6
Level 6· Lektion 6 von 8

MCP-Auth. OAuth 2.1 und Magic Link ohne Pain

Wie Du Deinen MCP-Server sicher öffentlich machst ohne dass Du Bearer-Tokens rumschicken musst. Die Schaltzentrale für jede SaaS-Version.

Wenn Du Deinen MCP-Server nur bei Dir selbst betreibst, brauchst Du keine Authentifizierung. Du vertraust Dir selbst. Sobald Du ihn für andere User öffentlich machst, ist Auth Pflicht, sonst schreibt der erste beliebige Angreifer in Deine Datenbank. In dieser Lektion zeigen wir wie das heute sauber geht.

Warum OAuth 2.1 und nicht nur Bearer-Tokens

Der einfache Weg wäre: Jeder User bekommt einen API-Key, trägt den in seine claude_desktop_config.json ein, fertig. Tatsächlich akzeptieren viele MCP-Server das so. Das Problem ist:

  • Der Key liegt als Klartext in einer Config-Datei. Verliert der User sein Gerät, ist der Key weg.
  • Du kannst den Key nicht einfach widerrufen ohne dass der User einen neuen ziehen und überall neu eintragen muss.
  • Es gibt keine Scopes (nur "alles" oder "nichts").
  • Kein Refresh-Flow bei Ablauf.

OAuth 2.1 mit PKCE löst alle diese Punkte. Der User klickt einmal auf "anmelden", bekommt einen Magic-Link in die E-Mail, der Client tauscht das automatisch gegen einen Token, alles läuft hinter den Kulissen. Dem User ist das fast unsichtbar. Dir als Betreiber gibt es die volle Kontrolle.

Was bei MCP anders ist als bei normalem OAuth

Ein normales Web-OAuth läuft im Browser. MCP-Clients sind nicht immer Browser, es sind Apps wie Claude Desktop, Codex, Cursor. Die Spec verlangt:

  • Der MCP-Server stellt unter /.well-known/oauth-authorization-server seine OAuth-Metadata bereit.
  • Der Client erkennt beim ersten Tool-Call dass Auth nötig ist, öffnet den Browser des Users, leitet zum Authorization-Endpoint.
  • PKCE ist Pflicht (Code Challenge + Verifier), weil Mobile-Clients und Desktop-Apps keinen Client-Secret sicher halten können.
  • Der Refresh-Token-Flow muss unterstützt werden, damit die Session länger als eine Stunde hält ohne dass der User sich ständig neu einloggen muss.

Der Magic-Link-Trick

Statt Passwörter zu verwalten, nutzt Du Magic Links. Der User gibt seine E-Mail ein, Du schickst eine E-Mail mit einem einmaligen Link, der Link führt zurück auf den Authorization-Server und gibt dort den Code frei. Der Client tauscht ihn in einen Token um.

Vorteile:

  • Kein Password-Reset-Flow (grösste Support-Quelle in SaaS).
  • User kann nicht "falsches Passwort" haben.
  • Die E-Mail-Adresse ist gleichzeitig Login + Account-Identifier.
  • Funktioniert auch wenn der User sein Gerät wechselt.

Nachteile:

  • Du musst SMTP sauber eingerichtet haben (DKIM, SPF, gutes Sender-Score). Sonst landen Deine Magic Links im Spam.
  • Kein Offline-Login (ohne E-Mail-Zugriff kein Login).

Für die meisten MCP-Server-Anwendungen ist der Trade-off eindeutig: Magic Link gewinnt.

Die vier Endpunkte die Du bauen musst

  1. GET /.well-known/oauth-authorization-server. Metadata. Statisches JSON, einmal schreiben, nie wieder anfassen.
  2. GET /authorize. User-facing Login-Seite. Zeigt ein E-Mail-Formular, verschickt Magic Link, zeigt "prüf deine E-Mail".
  3. GET /callback (vom Magic Link Ziel), konsumiert den einmaligen Code, erzeugt den Authorization-Code, leitet zurück zum Client.
  4. POST /token, tauscht den Authorization-Code gegen Access-Token + Refresh-Token. Muss PKCE prüfen.

Das war's. Keine weiteren Endpunkte nötig. Access-Token hat 1 Stunde Lebenszeit, Refresh-Token hat ein paar Tage bis Wochen.

Scopes

Definiere mindestens drei:

  • read, nur Lese-Tools aufrufen.
  • write, schreibende Tools erlaubt.
  • admin. Management-Tools (Löschen, Exportieren, Config ändern).

Die meisten User bekommen read + write. Admin ist für Dich oder explizit gewährte Fälle.

Bei der Authorization-Anfrage gibt der Client die gewünschten Scopes an. Du zeigst sie dem User im Login-Screen ("diese App möchte: lesen, schreiben"), der User akzeptiert oder lehnt ab.

Token-Storage im MCP-Client

Der Client (Claude Desktop, Codex) speichert das Token typischerweise verschlüsselt im System-Keychain (Mac) oder im Credential-Manager (Windows). Du musst Dich darum als Server-Betreiber nicht kümmern.

Was Du tun solltest: Token-Ablauf sauber kommunizieren. Wenn ein Token abgelaufen ist, gib einen klaren 401 mit WWW-Authenticate: Bearer error="invalid_token" zurück, dann weiss der Client dass er refreshen soll.

Fehler die Du machen wirst

  • State-Parameter vergessen. Der OAuth-State-Parameter schützt gegen CSRF. Ohne den, kann ein Angreifer Dich in einen Login redirekten. Pflicht.
  • PKCE-Verifier nicht geprüft. Wenn Dein Server den Verifier nicht wirklich gegenprüft, ist PKCE wertlos.
  • Refresh-Token mit kurzer Lebensdauer. Wenn der Refresh-Token nach einer Stunde abläuft, muss sich der User ständig einloggen. Refresh-Token ist nicht Access-Token. 14 Tage oder mehr sind üblich.
  • Keine Token-Revocation. Wenn ein User sein Gerät verliert, muss er das Token widerrufen können. Dashboard-Endpunkt bauen.
  • Scope-Check im Handler vergessen. Jeder Tool-Handler muss prüfen ob der eingereichte Token den nötigen Scope hat. Sonst umgeht man die ganze Arbeit in einer Zeile Code.

Tenant-Isolation, der wichtigste Fehler vermeiden

Wenn Dein Server mehrere User hat, muss JEDE Datenbank-Query einen tenant_id-Filter haben. User A darf User B's Daten nicht sehen. Das klingt offensichtlich, wird aber fast immer irgendwo vergessen, bei findUnique, bei Suchfunktionen, bei Batch-Löschungen.

Empfohlener Pattern: tenant_id in jedem DB-Model als Pflichtfeld + statischer CI-Scanner der alle Prisma/SQL-Queries prüft ob tenant_id drin ist. Ein Bug hier ist ein Datenleak über User-Grenzen hinweg, der schwerste Fehler den ein SaaS-Server machen kann.

Minimum-Viable-Beispiel

Der Kern-Flow in Pseudocode:

POST /authorize  (mit email, client_id, code_challenge, state)
  -> erzeuge magic_token (random), speichere {email, client_id, code_challenge, state, ablauf: +15min}
  -> schicke E-Mail mit Link: /callback?magic_token=xxx
  -> zeige "prüf deine E-Mail" Seite

GET /callback?magic_token=xxx
  -> lookup magic_token, invalidiere sofort
  -> erzeuge authorization_code (random, 5min gültig)
  -> redirect zu client_redirect_uri?code=xxx&state=original_state

POST /token (mit code, code_verifier)
  -> verifiziere code existiert, nicht abgelaufen, nicht verbraucht
  -> verifiziere PKCE: SHA256(code_verifier) == ursprüngliches code_challenge
  -> erzeuge access_token + refresh_token, gib zurück
  -> markiere code als verbraucht

Das ist der komplette Flow. Alles andere ist Deko.

Was Du in Produktion noch brauchst

Rate-Limiting auf /authorize (sonst Spam). HMAC-signierte Tokens (Pepper+Secret). Logging pro Login-Versuch. Admin-Dashboard für Token-Übersicht und -Widerruf. Alles ausgelagerte Sorgen, aber keine Show-Stopper. Du kannst mit dem minimalen Flow anfangen und nachrüsten.

Weiter

Das StudioMeyer Memory MCP (memory.studiomeyer.io) nutzt genau dieses Pattern. Der OSS-Mirror für das portable Memory-Pattern liegt unter github.com/studiomeyer-io/local-memory-mcp. Der Auth-Layer selbst lebt im privaten Hauptrepo, aber das Pattern ist dokumentiert und lässt sich portieren.

Lesson 7 zeigt wie sich mehrere authentifizierte MCP-Server zu einem Multi-Agent-Setup kombinieren. Das ist das Skalierungs-Kapitel.

Du liest ohne Account. Login speichert Deinen Fortschritt, damit Du beim nächsten Mal direkt weitermachen kannst. Einloggen →