CodeGym /Kurse /ChatGPT Apps /Warum Authentifizierung in der ChatGPT App nötig ist und ...

Warum Authentifizierung in der ChatGPT App nötig ist und eine kurze Evolution von OAuth

ChatGPT Apps
Level 10 , Lektion 0
Verfügbar

1. Warum braucht man überhaupt Authentifizierung in der ChatGPT App

Fangen wir mit dem Wichtigsten an: der Benutzer in ChatGPT ≠ der Benutzer in Ihrem Service.

ChatGPT hat ein eigenes Benutzerkonto. Ihr Service hat eigene userId, tenantId, Rollen, Abrechnung, Bestellungen. Zwischen ihnen gibt es standardmäßig keine magische Verknüpfung. Wenn Sie einfach einen MCP-Server starten und ein paar Tools beschreiben, wird ChatGPT sie als einen abstrakten Client aufrufen.

Erinnern wir uns an unser hypothetisches Beispiel GiftGenius — eine ChatGPT App, die hilft, Geschenke auszuwählen und Wunschlisten zu verwalten. Was wir können wollen:

  • Dem Benutzer seine gespeicherten Geschenklisten anzeigen.
  • Erlauben, Geschenke als „gekauft“ oder „erhalten“ zu markieren.
  • Die Bestellhistorie anzeigen (insbesondere wenn wir später in Richtung Commerce/ACP gehen).

Ohne Authentifizierung weiß der MCP-Server überhaupt nicht, „wer das ist“. Maximal sieht er einige technische Verbindungskennungen und ein anonymes Subject, das OpenAI zur Identifizierung und für Rate-Limits bereitstellt, aber ausdrücklich warnt, dass es nicht für die Autorisierung verwendet werden soll.

Authentifizierung vs. Autorisierung

Es ist sehr hilfreich, zwei Begriffe direkt zu trennen.

  • Authentifizierung (AuthN) beantwortet die Frage: Wer ist das?
  • Autorisierung (AuthZ) beantwortet: Was darf dieser „Jemand“ tun?

Für die ChatGPT App sieht das Schema ungefähr so aus:

  1. Zuerst bestätigen Sie über OAuth, dass der Benutzer tatsächlich bei Ihrem Identity Provider (IdP) (z. B. Keycloak/Auth0) angemeldet ist, und erhalten ein Token mit seiner Kennung. Das ist Authentifizierung.
  2. Danach liest der MCP-Server das Token, extrahiert daraus sub, Rollen und andere Claims und entscheidet, ob dieser Benutzer ein bestimmtes Tool aufrufen darf (list_orders, delete_profile usw.). Das ist Autorisierung.

Auf Code-Ebene kann man es so vorstellen (vereinfacht):

// Datentyp, den der MCP-Server über den Benutzer wissen möchte
export interface AuthContext {
  userId: string;
  roles: string[];
}

// Beispiel für die Verwendung in einem Tool-Handler
async function listGiftLists(auth: AuthContext | null) {
  if (!auth) {
    throw new Error("User is not authenticated");
  }

  // Aus der DB nur die Listen dieses Benutzers holen
  return db.giftLists.findMany({ where: { ownerId: auth.userId } });
}

Ohne userId und Rollen können Sie die Geschäftslogik schlicht nicht korrekt schreiben. Alles würde zu „einem großen gemeinsamen Konto für alle“ verkommen.

2. Warum „API-Schlüssel in .env“ keine Lösung ist

Wir Entwickler haben einen natürlichen Reflex: „Ich mache einen API-Schlüssel, lege ihn in .env, und alles wird funktionieren.“ Und tatsächlich, für interne Service-zu-Service-Integrationen sind API-Schlüssel ein normales Werkzeug. Aber sobald echte Benutzer und die ChatGPT App ins Spiel kommen, bricht der Ansatz „ein Schlüssel für alle“.

Schauen wir uns typischen Code aus frühen Modulen an, in denen wir einfach vom MCP auf unser Backend zugegriffen haben:

// mcp/backendClient.ts
export const backendClient = new BackendClient({
  baseUrl: process.env.BACKEND_URL!,
  apiKey: process.env.BACKEND_API_KEY!, // ein Schlüssel für das gesamte ChatGPT
});

Aus Sicht des Backends sehen jetzt alle Anfragen gleich aus: „Das ist die ChatGPT-Integration.“ Kein Unterschied zwischen Masha und Pasha. Das bedeutet:

  • Kein „Persönlicher Bereich“ möglich — der Server weiß nicht, wem er gehört.
  • Keine Trennung von Rechten: „Dieser Benutzer darf nur lesen, jener auch kaufen.“
  • Keine Verknüpfung von Bestellungen mit einer Person in Ihrem Kernsystem.

In der MCP-Welt ist das zudem unsicher. Die Spezifikation empfiehlt, HTTP-Authentifizierung (Bearer, API-Schlüssel usw.) über Streamable HTTP zu verwenden, betont aber, dass vollständiger Benutzerzugriff auf geschützte Ressourcen besser über OAuth und Tokens als über einen einzigen Service-Schlüssel aufgebaut wird.

Außerdem gilt aus Sicht der OpenAI-Policy: Eine gute App sollte nur die Daten anfragen, die wirklich benötigt werden, und dem Benutzer Kontrolle darüber geben, was er mit der App teilt. Das passt hervorragend zum OAuth-Scopes-Modell, aber überhaupt nicht zum Ansatz „ein Super-Schlüssel, der alles kann“.

Warum ist ein Service-Schlüssel im Kontext von ChatGPT problematisch

Ein Service-API-Schlüssel drückt die Identität des Dienstes aus, nicht die des Benutzers. Damit können Sie Aufrufe von Ihrem MCP-Server zu internen Services oder zu externen APIs (wie der OpenAI API) signieren, aber nicht sagen: „Das ist Vasya, zeig ihm seine Bestellhistorie.“

Ein einfaches Anti-Beispiel:

// Schlechtes Beispiel: "Benutzer-Täuschung"
async function getMyOrdersFromBackend() {
  // Der MCP-Server ruft /orders/me am Backend auf
  const res = await fetch(`${BACKEND_URL}/orders/me`, {
    headers: {
      Authorization: `Bearer ${process.env.BACKEND_API_KEY}`,
    },
  });

  // Das Backend nimmt an, dass "me" ein Integrationsservice ist, nicht ein Mensch
  return res.json();
}

Selbst wenn Sie versuchen, künstlich irgendeine anonyme userId in den Anfrage-Body einzubauen, bleibt es ein „Bastel-Fahrrad“. Sie brauchen trotzdem:

  • Einen verlässlichen Weg, dem Backend zu beweisen, dass „dies tatsächlich Vasya ist und nicht jemand anderes“.
  • Eine Möglichkeit, die Rechte eines konkreten Benutzers zu begrenzen.
  • Einen Mechanismus zum Widerrufen (revoke) des Zugriffs für einen bestimmten Benutzer, nicht für alle auf einmal.

Und hier kommt OAuth ins Spiel.

3. Mini-Glossar: Was wir von einem Login-System überhaupt wollen

Bevor wir in die Geschichte von OAuth springen, formulieren wir einfach die Anforderungen an ein „normales“ Authentifizierungssystem für die ChatGPT App.

Wir brauchen einen Ansatz, bei dem:

  1. Unser externer IdP (Keycloak, Auth0, Hydra+Kratos usw.) den realen Benutzer kennt: Login, E-Mail, userId, eventuell Tenant.
  2. Dieser IdP ein kurzlebiges Token ausstellt, das ChatGPT sicher an den MCP-Server im HTTP-Header Authorization: Bearer <token> übergeben kann.
  3. Der MCP-Server das Token liest und die Signatur prüft, Issuer, Audience, Ablaufzeit und Scopes überprüft, sub (Benutzerkennung) extrahiert und darauf basierend den Benutzer auf eigene Entitäten mappt (accountId, tenantId).
  4. Dieselben Scopes erlauben eine feine Rechteverwaltung: Ein Token gibt nur read:gifts, ein anderes zusätzlich write:gifts oder checkout.
  5. Falls das Token fehlt oder die Scopes nicht passen, kann der Server einen Fehler zurückgeben mit _meta["mcp/www_authenticate"], damit ChatGPT dem Benutzer ein Autorisierungs-UI anzeigt und/oder ein Token neu erwirbt.

Kurz: Wir brauchen ein standardisiertes, bewährtes Protokoll, das all das kann. Spoiler: Das ist OAuth 2.1 (und seine „älteren/jüngeren Geschwister“).

4. Kurze Evolution von OAuth: von Dinosauriern bis PKCE

Gehen wir nun vorsichtig die Evolution von OAuth durch, ohne tief in RFCs einzutauchen, aber mit dem Verständnis, warum uns gerade moderne Patterns interessieren.

OAuth 1.0 / 1.0a: Krypto-Fitness

Historisch kam zuerst OAuth 1.0. Es erlaubte Websites, anderen Diensten Zugriff auf ihre Ressourcen zu geben, ohne das Passwort des Benutzers weiterzugeben (was schon gut ist). Aber:

  • Die Signaturen der Anfragen waren kompliziert: HMAC-Signierung fast jeder Anfrage, Base-Strings, Normalisierung der Parameter.
  • Jede Anfrage musste signiert werden, der Consumer-Secret musste gespeichert und die Signatur korrekt gebildet werden.

Die meisten modernen Entwickler haben wenig Lust, all diese Rituale manuell nachzubauen.

Die Spezifikation 1.0a hat einige Schwachstellen behoben, die generelle Schwerfälligkeit blieb jedoch.

OAuth 2.0: Framework statt „ein Protokoll“

OAuth 2.0 hat vieles vereinfacht: Statt eines strikt vorgeschriebenen Schemas gab es einen Satz von Flows (Authorization Code, Implicit, Resource Owner Password, Client Credentials usw.). Das brachte Flexibilität, erzeugte aber auch einen Zoo an Implementierungen.

Vorteile:

  • Einfachere Integration von SPAs, mobilen und Server-Anwendungen.
  • Klare Trennung der Rollen: Resource Owner, Client, Resource Server, Authorization Server.

Nachteile:

  • In der Praxis entstanden viele riskante „Shortcuts“. Der Flow implicit (der das Token direkt im Browser ohne serverseitigen Code-Austausch auslieferte) erwies sich als unsicher.
  • Der Flow password grant (bei dem der Client einfach Login/Passwort des Benutzers gegen ein Token eintauscht) widerspricht der Grundidee von OAuth — und wurde zum Anti-Pattern.

Die Spezifikation ließ zu viel „zur Auswahl“ offen, daher entstanden zahlreiche Empfehlungen und Best Practices, die in separaten RFCs und Blogposts lebten.

OAuth 2.1: gesammelt, durchgeatmet, aufgeräumt

OAuth 2.1 ist der Versuch, Best Practices zu dokumentieren, die sich bis dahin bereits in der Community etabliert hatten:

  • Fokus fast vollständig auf dem Authorization Code Flow als Hauptvariante.
  • PKCE (Proof Key for Code Exchange) ist zwingend für Public-Clients — also jene, die kein Secret sicher speichern können (z. B. Mobile Apps, SPAs und … ChatGPT/MCP-Clients).
  • Veraltete und unsichere Flows wie Implicit und Password Grant sind aus der Spezifikation entfernt.
  • Empfehlungen für kurze Lebensdauer von Access Tokens und die Nutzung von Refresh Tokens für langlebige Sessions.

Warum ist das für Sie wichtig? Weil das Ökosystem rund um MCP und ChatGPT sich offenkundig genau an diesen Best Practices orientiert: Das Apps SDK und die MCP-Authorization-Spezifikation verlangen explizit Authorization Code + PKCE, kurzlebige Tokens und saubere Scopes.

5. Warum wir im Kontext der ChatGPT App in Mustern von OAuth 2.1 + PKCE denken

Mit dem historischen Kontext im Hinterkopf betrachten wir das nun durch die Linse von ChatGPT und MCP.

ChatGPT als Public Client

ChatGPT (und Clients wie MCP Jam) ist gegenüber Ihrem Auth Server ein typischer Public Client:

  • Er hat kein und kann kein verlässlich gespeichertes client_secret haben.
  • Er läuft in der OpenAI-Infrastruktur, die Sie nicht kontrollieren.

Daher ist die einzige sinnvolle Wahl — Authorization Code Flow + PKCE, bei dem die Sicherheit nicht auf einem Client-Secret beruht, sondern auf der Prüfung von Code-Challenge und Code-Verifier.

Die offizielle Apps-SDK-Dokumentation sagt eindeutig, dass ChatGPT als MCP-Client den Flow mit Authorization Code + PKCE (S256) ausführt und die Autorisierung verweigert, wenn Ihr Authorization Server die PKCE-Unterstützung nicht in den Metadaten deklariert: code_challenge_methods_supported: ["S256"].

Wie der Flow aus Sicht von MCP aussieht

Sehr grob, aber hilfreich, kann man sich das für eine geschützte Ressource so vorstellen:

sequenceDiagram
    participant U as Benutzer
    participant C as ChatGPT (MCP Client)
    participant AS as Auth Server
    participant RS as MCP Server (Resource)

    U->>C: "Zeige meine Bestellungen"
    C->>RS: call_tool(list_orders) ohne Token
    RS-->>C: Fehler + _meta["mcp/www_authenticate"]
    C->>AS: Öffnet Login/Consent (Authorization Code + PKCE)
    U->>AS: Meldet sich an und erteilt Zustimmung (Scopes)
    AS-->>C: Authorization Code
    C->>AS: Tauscht Code gegen Access Token (+PKCE-Prüfung)
    AS-->>C: Access Token (Bearer)
    C->>RS: call_tool(list_orders) mit Authorization: Bearer <token>
    RS->>RS: Prüfung von Signatur, Issuer, Audience, Scopes
    RS-->>C: Liste der Bestellungen des Benutzers
    C-->>U: Zeigt die Daten

Der Server nutzt dabei:

  • Metadaten der geschützten Ressource (/.well-known/oauth-protected-resource) — dort deklariert er sich als Ressource und gibt an, welcher Authorization Server diese Ressource bedient.
  • Das Token, das im Header Authorization: Bearer <token> ankommt, das er entweder als JWT per JWK prüft oder über den Auth Server introspektiert.
  • Falls das Token nicht zur Audience oder den Scopes passt — kann der Server die Anfrage ablehnen und erneut einen WWW-Authenticate-Challenge in _meta["mcp/www_authenticate"] zurückgeben, damit ChatGPT die Autorisierung mit den benötigten Parametern erneut durchläuft.

Aus Sicht Ihres Codes wirkt das alles human: Sie erhalten als Input bereits einen geprüften AuthContext und arbeiten damit.

Mini-Beispiel: wie ein MCP-Tool zwischen anonymem und authentifiziertem Benutzer unterscheidet

Noch ohne konkretes OAuth-SDK, nur das Konzept:

import type { McpToolHandler } from "./types";

export const listOrders: McpToolHandler = async (_args, context) => {
  const auth = context.auth; // nehmen wir an, hier liegt das Ergebnis der Tokenprüfung

  if (!auth) {
    return {
      content: [{ type: "text", text: "Sie müssen sich anmelden, um Bestellungen zu sehen." }],
      _meta: {
        // Challenge für ChatGPT: starte den OAuth-Flow
        "mcp/www_authenticate": [
          'Bearer resource_metadata="https://mcp.giftgenius.app/.well-known/oauth-protected-resource", error="insufficient_scope", error_description="Login required to view orders"'
        ]
      },
      isError: true
    };
  }

  const orders = await db.orders.findMany({ where: { userId: auth.userId } });

  return {
    content: [{ type: "text", text: `Gefundene Bestellungen: ${orders.length}` }],
    structuredContent: orders
  };
};

Genau ein solcher _meta["mcp/www_authenticate"] Hinweis ist in der offiziellen Apps-SDK-Dokumentation als Trigger für das OAuth-UI auf Seiten von ChatGPT beschrieben.

6. Was „kurzlebiger Token, minimale Scopes“ in der Praxis bedeutet

Aus Spezifikationen und Leitfäden ergeben sich noch ein paar wichtige Prinzipien, die man bereits jetzt im Kopf behalten sollte — vor der nächsten Vorlesung zur konkreten IdP-Konfiguration.

Kurze Lebensdauer des Tokens

Ein Access Token sollte nur kurz leben. Warum?

  • Wenn es geleakt wird, ist ein Angreifer zeitlich trotzdem stark begrenzt.
  • Sie können die Benutzerrechte sicher ändern, und nach kurzer Zeit „läuft das Token ab“ und wird neu angefordert.

Üblicherweise sind das Minuten oder einige Dutzend Minuten. Im Gegenzug gibt es Refresh Tokens und/oder erneute Autorisierungen, aber im Kontext von ChatGPT übernimmt die Client-Seite den Großteil der Routine.

Scopes als Mittel zur Rechtebegrenzung

Scopes sind Zeichenketten wie gifts.read, gifts.write, orders.read, orders.checkout. Sie geben an, wofür der Benutzer innerhalb dieser Ressource Rechte hat.

Für die ChatGPT App ist das besonders wichtig:

  • Sie können ein Token nur mit gifts.read ausstellen, wenn der Benutzer lediglich Wunschlisten betrachtet.
  • Für ACP/Instant Checkout ist es sinnvoll, einen strengeren Satz an Rechten zu verlangen — z. B. orders.checkout — und das dem Benutzer deutlich zu machen.

In der MCP-Beschreibung der Tools gibt es bereits die Möglichkeit, securitySchemes mit konkreten Scopes für Tools zu deklarieren, damit ChatGPT weiß, welche Rechte für den Aufruf eines bestimmten Tools nötig sind.

Audience: Der Token muss „für diese“ MCP-Ressource sein

Ein weiteres wichtiges Detail — aud (Audience). Der MCP-Server sollte prüfen, dass das Token tatsächlich für ihn ausgestellt wurde und nicht für irgendeinen Nachbarservice.

In der Apps-SDK-Dokumentation steht ausdrücklich, dass ChatGPT den Parameter resource durchreicht und erwartet, dass der Authorization Server ihn im Token reflektiert (typischerweise in aud) und der MCP-Server dieses Feld prüft.

Es ist gut möglich, dass im Review Ihrer App gefälschte auth_token übergeben werden, um auf Sicherheitslücken zu testen. Implementieren Sie es also gleich korrekt.

7. Wie das auf unsere Anwendung GiftGenius passt

Konzentrieren wir uns wieder auf unsere Übungs-App. Derzeit haben wir ungefähr folgendes Bild:

  • Es gibt das MCP-Tool get_gift_ideas, das anhand einer Empfängerbeschreibung und eines Budgets Geschenkideen vorschlägt. Das kann anonym funktionieren.
  • Es gibt das MCP-Tool save_gift_list, das eine Liste in der DB speichert. Es soll an einen konkreten Benutzer gebunden sein.
  • Es gibt das MCP-Tool list_saved_lists, das alle vom Benutzer gespeicherten Listen anzeigt. Das erfordert definitiv Authentifizierung.

Das Widget zeigt hübsche Geschenk-Karten, erlaubt „speichern“ und „als gekauft markieren“ — all das ist im Grunde ein Frontend für geschützte MCP-Tools.

Auf Typ-Ebene kann das so aussehen:

// Typisierung des Tool-Aufrufkontexts (vereinfacht)
interface ToolContext {
  auth: AuthContext | null;
}

// Beispiel für ein geschütztes Tool
async function listSavedGiftLists(_input: {}, context: ToolContext) {
  if (!context.auth) {
    // Hier wird derselbe Trick mit mcp/www_authenticate verwendet wie oben
    throw new Error("Authentication required");
  }

  return db.giftLists.findMany({
    where: { ownerId: context.auth.userId }
  });
}

Sobald Sie solche Funktionen schreiben, wird klar: „Nur ein API-Schlüssel in .env“ hilft hier nicht. Es braucht einen vollwertigen AuthContext, der auf einem verifizierten OAuth-Token basiert.

Welche Teile der Anwendung können anonym arbeiten, und welche nicht

Eine gute Übung vor der OAuth-Konfiguration — den Funktionsumfang ehrlich in zwei Kategorien teilen.

Zum Beispiel in GiftGenius:

Anonym:

  • Generieren von Geschenkideen anhand einer Beschreibung.
  • Anzeige von Beispielen und Demo-Modus mit Dummy-Daten.

Nur für authentifizierte Benutzer:

  • Ansicht und Bearbeitung persönlicher Wunschlisten.
  • Bestellhistorie.
  • Jegliche Zahlungsvorgänge, Instant Checkout, Anbindung an ACP.

In den nächsten Vorlesungen werden wir den Auth Server (z. B. Keycloak oder ein Hydra+Kratos-Setup) und den MCP-Server so konfigurieren, dass Tokens für diese Aktionen die passenden Scopes haben und die MCP-Tools korrekt ablehnen und ChatGPT um Re-Autorisierung bitten können.

8. Typische Fehler beim Verständnis der Authentifizierung in der ChatGPT App

Fehler Nr. 1: „ChatGPT kennt den Benutzer doch schon, wozu brauche ich meinen eigenen Login?“
Viele denken: „ChatGPT hat ein Benutzerkonto, warum nutze ich das nicht einfach als userId?“ Aber ChatGPT offenbart Ihnen nicht die reale Identität des Benutzers und gibt keinen Zugriff auf seine Konten. In den MCP-Metadaten sehen Sie allenfalls ein anonymes _meta["openai/subject"], das für Rate-Limits und Sitzungsidentifikation gedacht ist, aber ausdrücklich nicht für Autorisierung oder die Verknüpfung mit realen Accounts verwendet werden darf.

Fehler Nr. 2: „Ein API-Schlüssel für alle ist ok, es ist ja nur eine ‚Integration‘“
Der Ansatz „Wir backen in den MCP-Server einen API-Schlüssel für unser Backend ein und sind happy“ funktioniert nur in Szenarien, in denen alle ChatGPT-Benutzer dasselbe Konto in Ihrem Service teilen. Sobald persönliche Daten, Commerce, ACLs ins Spiel kommen — stoßen Sie an Grenzen: Benutzer lassen sich nicht unterscheiden und ihre Rechte nicht verwalten. Ein API-Schlüssel ist die Identität des Dienstes, nicht des Benutzers.

Fehler Nr. 3: „Wir nehmen den Password Grant, das ist am einfachsten“
Die Gewohnheit, Login/Passwort des Benutzers an Ihr Backend zu senden und gegen ein Token zu tauschen (Resource Owner Password Credentials Grant), ist ein veraltetes und unsicheres Pattern aus frühen OAuth‑2.0‑Zeiten. In modernen Empfehlungen und im Kontext von OAuth 2.1 gilt es als Anti-Pattern. Public-Clients wie ChatGPT sollten die Passwörter Ihrer Benutzer überhaupt nicht sehen — dafür gibt es Authorization Code + PKCE.

Fehler Nr. 4: „PKCE ist unnötige Komplexität, lassen wir das weg“
PKCE (insbesondere S256) ist kein Marketing, sondern ein zwingender Schutzmechanismus des Authorization Code Flow für Public-Clients. Ohne PKCE kann ein gestohlener Authorization Code wiederverwendet werden. In der MCP-Authorization-Spezifikation und im Apps SDK steht ausdrücklich, dass ChatGPT die PKCE-Unterstützung in den Metadaten des Authorization Servers verlangt und genau diesen Mechanismus nutzt. Wenn Sie ihn deaktivieren, funktioniert der Flow schlicht nicht.

Fehler Nr. 5: „Wir fragen gleich alle möglichen Scopes an — sicher ist sicher“
Manchmal möchte man ein Token ausstellen, das „alles darf“. Das verletzt jedoch das Prinzip minimaler Rechte (PoLP) und kollidiert mit den Richtlinien von OpenAI wie auch den meisten IdPs. Besser ist es, genau zu durchdenken, welche Scopes Ihre ChatGPT App wirklich braucht: einige fürs Lesen, andere fürs Schreiben, separate fürs Commerce. Das erhöht nicht nur die Sicherheit, sondern verbessert auch das Consent‑UX: Der Benutzer sieht einen verständlichen und begrenzten Satz an Rechten statt einer erschlagenden Liste von zwanzig kryptischen Zeilen.

Fehler Nr. 6: „Der MCP-Server speichert Logins/Passwörter und zeichnet das Login-UI“
Der MCP-Server ist ein Resource Server, kein Auth Server. Er sollte Tokens prüfen, seine .well-known-Metadaten bereitstellen und WWW-Authenticate-Challenges ausgeben, aber nicht das Login durchführen oder Passwörter speichern. Für Login/Consent verwenden Sie besser einen spezialisierten Authorization Server (Keycloak, Hydra, Auth0 usw.), wie wir in den nächsten Vorlesungen sehen werden.

Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION