Auth0 + MCP CaptainDNS: unser vollständiger Erfahrungsbericht

Von CaptainDNS
Veröffentlicht am 4. Dezember 2025

  • #Auth0
  • #MCP
  • #OAuth2
  • #DNS
  • #Architektur
Architekturdiagramm mit Auth0, dem CaptainDNS-MCP-Server, der Backend-API und MCP-Clients
TL;DR

In diesem Erfahrungsbericht zeigen wir, wie wir Auth0 an den MCP-Server von CaptainDNS angeschlossen haben, ohne das Bestehende zu zerstören:

  • Ein MCP-Server auf /stream (dev und prod), der JSON-RPC mit MCP-Clients spricht (MCP Inspector, morgen ChatGPT).
  • Eine Backend-API, die bereits per Frontend an Auth0 hängt und mit einem neuen MCP-OAuth2-Flow aligned werden musste.
  • Bedarf an optionaler Auth: Tools müssen ohne Login laufen, aber die Identität soll genutzt werden, wenn ein Bearer vorliegt.
  • Eine eigene Auth0-API für den MCP, ein PRM (/.well-known/oauth-protected-resource) und das Resource Parameter Compatibility Profile.
  • Feingranulare Identitätsweitergabe (sub, email, scopes) in profiles und api_requests-Logs, mit der Option, Zugänge später über geschützte Tools zu verschärfen.

1. Kontext: warum Auth0 an den CaptainDNS-MCP hängen?

CaptainDNS hat bereits:

  • ein Next.js-Frontend mit Auth0 (klassischer Login, RS256-Tokens für die API),
  • eine Backend-API,
  • ein Datenmodell, das Profile und Requests speichert.

Der MCP-Server ändert das Bild:

  • ein neuer Einstieg in dev, dann prod;
  • MCP-Clients (MCP Inspector, später ChatGPT), die über OAuth2 ein Access Token holen wollen, um den MCP-Server aufzurufen;
  • die Notwendigkeit, die Identität bis zur Backend-API zu tragen, um Aktionen einem bestehenden Profil zuzuordnen.

Randbedingungen:

  • das bestehende Frontend nicht brechen (historische Audience, Tokens im Umlauf);
  • optionale Auth für die v0-Tools beibehalten (lookup, propagation, email audit);
  • eine saubere Basis für spätere "Auth Pflicht"-Tools legen (Historie, Resolve-Watchlist, Premium-Features etc.).

2. Überblick Auth0 + MCP

Am Ende sieht die Kette so aus:

  • Auth0 (XXX-prod.eu.auth0.com):

  • deklariert zwei APIs:

  • die historische Frontend-API,

  • die MCP-API (dev und prod);

  • gibt RS256-JWT-Access-Tokens für diese Audiences aus.

  • CaptainDNS-MCP-Server:

  • JSON-RPC-Endpoint: /stream (dev und prod);

  • veröffentlicht ein PRM (Protected Resource Metadata) unter /.well-known/oauth-protected-resource;

  • validiert Auth0-Tokens über Issuer und JWKS;

  • ruft die Backend-API auf und reicht Authorization: Bearer <access_token> plus source-Header (frontend_mcp_anonymous oder frontend_mcp_authenticated) weiter.

  • Backend-API:

  • akzeptiert mehrere Audiences (Frontend + MCP);

  • mappt Auth0-subprofiles;

  • protokolliert alle Requests in apirequests mit user_id und source.

  • MCP-Clients:

  • MCP Inspector in dev,

  • ChatGPT (Connectors) in prod, als OAuth2-MCP-Client.

Auth0 + MCP CaptainDNS Architekturdiagramm

Ziel: Bei einem tools/call von CaptainDNS:

  • anonymer Modus: Request läuft normal;
  • authentifizierter Modus: Request wird einem Profil zugeordnet.

3. Eigene Auth0-API für den MCP und Audience-Abgleich

3.1. Auth0-API für MCP: Audience = MCP /stream

Statt die historische Audience wiederzuverwenden, haben wir eine dedizierte Auth0-API für den MCP angelegt:

  • in dev:
  • audience = XXX-audience-dev;
  • in prod:
  • audience = XXX-audience-prod;
  • alg = RS256;
  • Access-Token-Format = JWT (signiert, nicht verschlüsselt).

MCP-Seite:

  • AUTH0_AUDIENCE zeigt auf diese Audience (dev oder prod);
  • das PRM (/.well-known/oauth-protected-resource) wiederholt die Werte in resource, audience und default_audience.

Backend-API:

  • AUTH0_ALLOWED_AUDIENCES enthält alle akzeptierten Audiences:
  • die historische Frontend-Audience,
  • die MCP-Audience.

3.2. Resource vs. Audience und verschlüsseltes JWE

Erstes Problem mit MCP Inspector:

  • der Client sendete nur resource=<url> in der Authorize-URL;
  • Auth0 erwartete ein explizites audience=<url>;
  • typisches Ergebnis:
  • entweder ein verschlüsseltes Access Token (5-teiliges JWE, alg=dir, enc=A256GCM) unbrauchbar für den MCP,
  • oder ein Auth0-Fehler ("service not found"), wenn die Audience keiner API entsprach.

Lösung: das Resource Parameter Compatibility Profile in Auth0 aktivieren:

  • Auth0 behandelt dann resource=<url> wie audience=<url> für Clients, die nur resource kennen;
  • das Token ist ein klassisches 3-teiliges RS256-JWT mit aud = MCP-Resource-URL (/stream).

Damit bekamen wir einen OAuth2-Flow, den MCP Inspector ohne Verhaltensänderung versteht.

4. MCP-PRM (/.well-known/oauth-protected-resource)

Damit MCP-Clients wissen, wie sie OAuth2 gegen den Server fahren, veröffentlicht der MCP ein PRM (Protected Resource Metadata) unter:

  • /.well-known/oauth-protected-resource

Dieses JSON beschreibt insbesondere:

  • resource / audience / default_audience;
  • die in diesem Kontext zu nutzenden OAuth2-Endpoints:
  • authorization_endpoint,
  • token_endpoint,
  • optional registration_endpoint;
  • jwks_uri (über die OpenID-Konfiguration von Auth0);
  • scopes_supported:
  • openid, profile, email, offline_access,
  • sowie Anwendungsscopes (captaindns:dns:read, captaindns:email:read, etc.);
  • default_scope:
  • typischerweise "openid profile email captaindns:dns:read".

Das PRM ist damit ein OAuth2-Vertrag speziell für den MCP, ergänzend zum standardmäßigen OpenID-Discovery von Auth0.

5. JWT-Validierung im MCP-Server

Steht der OAuth2-Flow, muss der MCP-Server jedes Bearer auf /stream prüfen:

  • Authorization: Bearer <access_token> lesen (falls vorhanden);
  • OpenID-Konfiguration von Auth0 holen:
  • issuer = XXX;
  • jwks_uri (Public Key);
  • validieren:
  • RS256-Signatur via JWKS,
  • iss = Auth0-Tenant,
  • aud = MCP-Audience (dev oder prod),
  • exp nicht abgelaufen.

Zusätzlich kann der MCP-Server:

  • die Claim scope prüfen (String, z. B. "openid profile email captaindns:dns:read");
  • prüfen, dass die geforderten Scopes (standard profile, email) vorhanden sind, wenn direkt auf MCP-Ebene gefiltert werden soll.

Wenn der Bearer ungültig ist:

  • im Modus "optionale Auth" gilt der Benutzer als anonym, kein Challenge;
  • im Modus "Auth Pflicht" eines Tools (siehe unten) liefert der MCP einen JSON-RPC-Fehler mit _meta["mcp/www_authenticate"], um den Login clientseitig auszulösen.

MCP-Authentifizierungssequenz mit Auth0

6. Identität zur Backend-API durchreichen

Nach JWT-Validierung im MCP muss der Server die Identität zur API weiterreichen:

  • Header Authorization: Bearer <Access Token Nutzer> hinzufügen;
  • einen source-Header (oder ähnlich) hinzufügen, typisiert:
  • frontend_mcp_anonymous, wenn kein Bearer oder Bearer ungültig;
  • frontend_mcp_authenticated, wenn Bearer gültig.

MCP-Logs ergänzen ein Event mcp_outbound_api mit:

  • path=/...;
  • has_bearer=true/false;
  • source=frontend_mcp_*.

In der API validiert das Middleware optional_auth erneut:

  • Token (Issuer, Audience ∈ AUTH0_ALLOWED_AUDIENCES, erforderliche Scopes);
  • wenn das Token gültig ist:
  • Request an ein Profil binden (siehe nächster Abschnitt);
  • sonst:
  • Request als anonym behandeln, Herkunft (source) bleibt geloggt.

Diese "doppelte Validierung" (MCP + API) hält die API unabhängig vom Aufrufweg (Frontend vs. MCP).

7. Integration mit profiles und api_requests

7.1. Profile anlegen / aktualisieren

Die Backend-API bietet POST /profile, das:

  • sub (Auth0-Subject) und email aus den Claims liest;
  • einen Eintrag in profiles erstellt oder aktualisiert:
  • Primärschlüssel: auth0_sub;
  • aktuelle E-Mail;
  • created_at, last_seen_at.

Vorbedingung in der API:

  • email muss gesetzt sein, sonst 422 (kein unvollständiges Profil anlegbar).

Gefundener Fehler: Das Access Token für MCP enthielt email standardmäßig nicht.

7.2. Namespacete E-Mail-Claim via Auth0 Action

Um eine nutzbare E-Mail zu garantieren, haben wir eine Auth0 Post-Login Action ergänzt:

exports.onExecutePostLogin = async (event, api) => {
  const ns = "NAMESPACE";
  if (event.user && event.user.email) {
    api.accessToken.setCustomClaim(`${ns}/email`, event.user.email);
  }
};

Erklärung:

  • Auth0 verlangt, dass Custom Claims im Access Token namespacet sind (URL oder URN);
  • die Claim NAMESPACE.email wird daher bei jedem Access Token gesetzt, wenn der Nutzer eine E-Mail hat.

Backend-Seite:

  • /profile liest zuerst email (falls Standard vorhanden);
  • sonst Fallback auf NAMESPACE/email.

Die Kombination beider Quellen hält den Code robust, kompatibel mit unterschiedlichen Token-Typen (Frontend vs. MCP).

7.3. api_requests: Requests dem richtigen user_id zuordnen

Für jeden API-Request erstellt das Logging-Middleware:

  • eine Zeile in api_requests;
  • wenn es ein sub gibt und profiles.GetByAuth0ID(sub) existiert:
  • wird user_id in apirequests gesetzt;
  • source (Frontend / MCP / Backend-Job) qualifiziert die Herkunft.

Anonymer vs. authentifizierter Flow im CaptainDNS-MCP

Ergebnis:

  • anonyme Requests (MCP ohne Bearer) haben user_id = NULL, source=frontend_mcp_anonymous;
  • authentifizierte Requests werden einem Profil zugeordnet, user_id ist gesetzt.

8. Scopes und AUTH0_REQUIRED_SCOPES

8.1. Backend: AUTH0_REQUIRED_SCOPES

Die API nutzt einen Config-Parameter:

  • AUTH0_REQUIRED_SCOPES (z. B. ["profile", "email"]),

um zu prüfen, dass die Claim scope alle geforderten Scopes enthält.

Das Middleware optional_auth:

  • wenn ein Bearer vorhanden ist:
  • validiert Signatur und Audience;
  • prüft, dass scope alle nötigen Scopes enthält;
  • sonst Auth als ungültig markieren (missing required scope), Request aber anonym weiterlassen (kein user_id).
  • wenn kein Bearer:
  • Request direkt als anonym behandeln.

Daher mussten die MCP-Tokens profile und email enthalten:

  • über scopes_supported und default_scope im MCP-PRM;
  • über die scope-Parameter der MCP-Clients im OAuth2-Flow.

8.2. Keine "magischen" Lösungen in Auth0

Scopes per Auth0 Action hinzuzufügen ist technisch möglich, aber wir haben bevorzugt:

  • saubere Konfiguration über:
  • das MCP-PRM (default_scope);
  • den /authorize-Request (scope=),
  • klare Scope-Prüfungen auf API- und MCP-Seite, ohne "Hardcoding" im Tenant.

9. Optionale Auth und geschützte Tools (RequiresAuth)

9.1. Anfangsproblem: alles wurde "Auth Pflicht"

In einer ersten Version schickte der MCP-Server sofort ein Challenge, sobald kein oder ein ungültiger Bearer auftauchte:

  • _meta["mcp/www_authenticate"] mit realm und benötigten scope.

Folge:

  • einige MCP-Clients interpretierten das als generellen Fehler,
  • obwohl die v0-Tools anonym nutzbar bleiben sollten.

9.2. RequiresAuth-Flag und Dual-Mode

Die Lösung: ein RequiresAuth-Flag auf MCP-Tools:

  • für alle bestehenden Tools (dns_lookup, dns_propagation, email_auth_audit):

  • RequiresAuth = false;

  • Verhalten:

  • ohne Bearer: anonyme Ausführung, kein Challenge;

  • mit gültigem Bearer: Ausführung + Profil-Zuordnung.

  • für künftige "Premium"-Tools (request_history, resolve_watch_list, etc.):

  • RequiresAuth = true;

  • Verhalten:

  • kein Bearer oder ungültiger Bearer:

  • MCP gibt einen JSON-RPC-Fehler zurück mit:

  • isError: true,

  • _meta["mcp/www_authenticate"] gesetzt, um Login zu triggern;

  • gültiger Bearer:

  • normale Ausführung mit Profil-Bindung.

So lassen sich geschützte Tools schrittweise einführen, ohne die öffentlichen Tools zu brechen.

9.3. Sicherheits-Metadaten in tools/list

Um für MCP-Clients klar zu sein, zeigen Tools jetzt einen Security-Abschnitt:

  • Typ oauth2;
  • erforderliche Scopes (für geschützte Tools).

Tests prüfen:

  • dass tools/list die Security-Infos liefert,
  • dass geschützte Tools _meta["mcp/www_authenticate"] zurückgeben, wenn der Bearer fehlt.

10. Mehrere Audiences (Frontend + MCP) in prod handhaben

10.1. Beobachtungen

In Produktion koexistieren zwei Arten von Tokens:

  • Frontend-Tokens:
  • aud = [XXX, YYY];
  • MCP-Tokens:
  • aud = "XXX".

In einer Zwischenphase akzeptierte die API nur die MCP-Audience (AUTH0_ALLOWED_AUDIENCES="XXX"), wodurch das Frontend brach (Audience-Mismatch).

10.2. Lösung: mehrwertige AUTH0_ALLOWED_AUDIENCES

Die Lösung war, auf API-Seite mehrere Audiences zu erlauben:

  • AUTH0_ALLOWED_AUDIENCES="XXX, YYY"

Das Middleware:

  • splittet den String auf ,;
  • trimmt Spaces;
  • akzeptiert Tokens, deren aud (String oder Array) mindestens eine erlaubte Audience enthält.

Ergebnis:

  • die API akzeptiert sowohl:
  • Frontend-Tokens (historische Audience),
  • MCP-Tokens (MCP-Audience);
  • der Übergang in eine Multi-Client-Welt (Frontend + MCP + weitere) wird sauber gehandhabt.

Aufbau eines Auth0-JWT für den CaptainDNS-MCP

FAQ: Häufige Fragen zu Auth0 + MCP CaptainDNS

Warum eine eigene Auth0-API für den MCP statt der historischen API-Audience?

Die historische Audience wurde bereits vom Frontend und bestehenden Tokens genutzt. Dieselbe Audience dem MCP zu geben, hätte die Konfiguration unklarer und schwerer auditierbar gemacht.

Mit einer dedizierten Auth0-API für den MCP (dev und prod) erhalten wir eine klare Trennung der Nutzungen, bessere Nachverfolgbarkeit und können den MCP-Vertrag unabhängig vom Frontend weiterentwickeln.

Warum war das erste Token verschlüsselt (5-teiliges JWE) und kein lesbares JWT?

Ohne Resource Parameter Compatibility Profile interpretiert Auth0 manche Flows falsch, in denen nur resource gesendet wird, vor allem wenn keine passende Audience gefunden wird.

In unserem Fall sendete der MCP-Client resource=... ohne explizites audience=.... Auth0 antwortete dann teils mit einem verschlüsselten JWE (5 Teile) oder mit einer Fehlermeldung.

Durch Aktivieren des Resource Parameter Compatibility Profile behandelt Auth0 resource als audience und liefert ein klassisches 3-teiliges RS256-JWT, das der MCP-Server validieren kann.

Wozu dient genau das PRM (/.well-known/oauth-protected-resource) im MCP?

Das PRM ist ein Metadaten-Dokument, das beschreibt, wie ein Client OAuth2 nutzen soll, um auf eine geschützte Ressource zuzugreifen:

  • was Resource / Audience ist;
  • welche Authorization- und Token-Endpoints zu verwenden sind;
  • welche Scopes unterstützt und empfohlen werden.

Für CaptainDNS ermöglicht das PRM Clients wie MCP Inspector oder ChatGPT, automatisch zu entdecken, wie sie ein Auth0-Token für den MCP bekommen, welche Scopes sie anfragen sollen und sich ohne aufwendige manuelle Konfiguration zu integrieren.

Wie lassen sich Scopes managen, ohne bestehende Clients zu brechen?

Vorgehen:

  • ein minimales Set erforderlicher Scopes (profile, email) in der API definieren;
  • die gesamte Anfrage nicht ablehnen, wenn diese Scopes fehlen, sondern in den anonymen Modus fallen;
  • striktes Scheitern (missing scope) nur für MCP-Tools mit RequiresAuth = true.

So bleiben die v0-Tools nutzbar, ohne die Auth0-Konfiguration bestehender Clients zu ändern, und ein schrittweises Härten für Premium-Tools wird möglich.

Wie stellt man ein MCP-Tool von 'optionaler Auth' auf 'Auth Pflicht' um?

Die Umstellung erfolgt in zwei Schritten:

  • das MCP-Tool mit RequiresAuth = true markieren;
  • MCP-Server-Seite:
  • kein oder ungültiger Bearer → isError=true mit _meta["mcp/www_authenticate"], um den Login auszulösen;
  • gültiger Bearer und ausreichende Scopes → normale Ausführung.

So lässt sich ein Tool auf "Auth Pflicht" heben, ohne die Backend-API zu ändern; die Security-Logik bleibt im MCP.

Glossar Auth0 + MCP CaptainDNS

MCP (Model Context Protocol)

Protokoll, das standardisiert, wie ein KI-Modell mit externen Tools (APIs, Services) spricht. Bei CaptainDNS ermöglicht es Clients wie MCP Inspector oder ChatGPT, DNS/E-Mail-Tools über einen dedizierten MCP-Server aufzurufen.

PRM (Protected Resource Metadata)

JSON-Dokument, das eine geschützte Ressource (hier, der CaptainDNS-MCP-Server) unter /.well-known/oauth-protected-resource veröffentlicht. Es beschreibt Resource, Audience, Scopes sowie Authorization- und Token-Endpoints.

Issuer (iss)

JWT-Claim, der angibt, wer es ausgestellt hat. MCP-Server und Backend-API prüfen, dass iss dem erwarteten Auth0-Tenant entspricht.

Audience (aud)

Claim, das angibt, für welche Ressource(n) das Token bestimmt ist. Hier: die Frontend-API oder die MCP-API. Die Backend-API muss mehrere Audiences akzeptieren, um verschiedene Clients zu bedienen.

Subject (sub)

Eindeutige Kennung des Nutzers im Auth0-Tenant. Sie ist der Schlüssel, um das Profil in der Tabelle profiles zu finden oder anzulegen.

Scope

Liste von Berechtigungen eines Tokens (z. B. openid profile email captaindns:dns:read). Scopes steuern den Zugriff auf Funktionen, insbesondere auf geschützte MCP-Tools.

JWS vs JWE

  • JWS: signiertes JWT (3 Teile), serverseitig lesbar und verifizierbar.
  • JWE: verschlüsseltes JWT (5 Teile), das einen Entschlüsselungs-Key braucht. Für den CaptainDNS-MCP nutzen wir RS256-JWS, einfacher via JWKS zu validieren.

Namespaced claim

Custom-Claim im Access Token, dessen Name eine URL/URN ist, von Auth0 gefordert, um Kollisionen mit Standard-Claims zu vermeiden. Beispiel: NAMESPACE zum Speichern der E-Mail in einem MCP-Token.

Resource Parameter Compatibility Profile

Auth0-Option, die resource als audience behandelt für OAuth-Clients, die nur resource kennen. Wichtig, damit MCP Inspector ohne Verhaltensänderung ein gültiges RS256-JWT erhält.

frontend_mcp_anonymous / frontend_mcp_authenticated

Werte des Felds source, das in Logs und der Tabelle api_requests genutzt wird, um anonyme MCP-Requests (kein oder ungültiger Bearer) von authentifizierten MCP-Requests (gültiger Bearer, identifiziertes Profil) zu trennen.

Ähnliche Artikel

CaptainDNS · 27. November 2025

Diagramm der CaptainDNS-MCP-Architektur zwischen ChatGPT, dem MCP-Server und der Backend-API

Hinter den Kulissen des CaptainDNS-MCP

Wie wir CaptainDNS über MCP an AIs angebunden haben: Architektur, HTTP+SSE-Transport, JSON-RPC, 424-Fehler, Timeouts und unsere Learnings.

  • #MCP
  • #Architektur
  • #DNS
  • #E-Mail
  • #KI-Integrationen

CaptainDNS · 21. November 2025

Diagramm: Ein KI-Host spricht über einen standardisierten MCP-Connector mit CaptainDNS

Ein MCP für CaptainDNS?

Bevor CaptainDNS an IAs angebunden wird, muss klar sein, was das Model Context Protocol (MCP) ist und was es wirklich ermöglicht. Ein MCP-ABC und erste Ansätze für CaptainDNS.

  • #MCP
  • #KI
  • #DNS
  • #E-Mail
  • #Architektur