1. Was ist ein Auth-Server in der Praxis und warum verwenden wir Keycloak
Beginnen wir mit einer kurzen Erinnerung: Ein Auth‑Server (IdP) ist ein Dienst, der:
- dem Benutzer Login-/Registrierungsbildschirm und Consent anzeigt;
- OAuth/OIDC‑Tokens ausgibt (access_token, id_token, refresh_token);
- ein Discovery‑Dokument und JWKS‑Schlüssel veröffentlicht, damit Resource‑Server diese Tokens prüfen können.
In unserem Stack:
- ChatGPT / MCP Jam agiert als OAuth‑Client (public client);
- Ihr MCP‑Server – als Resource Server;
- Keycloak – als Auth Server.
Warum Keycloak sowohl für den Kurs als auch in der Praxis geeignet ist:
- es ist Open Source und lässt sich leicht lokal/in Docker starten;
- es hat ein recht transparentes Entitätenmodell: realm, clients, users, roles;
- im Grunde lässt sich jede Konfiguration, die Sie in Keycloak lernen, fast 1:1 auf Auth0/Okta/Cognito übertragen: dort gelten dieselben Ideen – client, scopes, redirect URIs, PKCE.
Wichtige Idee: Wir konfigurieren nicht „Keycloak für das gesamte Projekt“, sondern einen spezifischen Realm für unsere ChatGPT‑App. Das ist so etwas wie eine „Sandbox“ für die Authentifizierung genau der MCP‑Clients.
Kurz gesagt, am Ende der Vorlesung haben Sie:
- einen eigenen Realm in Keycloak für die ChatGPT‑Anwendung;
- einen konfigurierten Public‑Client mit Authorization Code + PKCE;
- ein minimales Set an Scopes und Claims;
- ein Verständnis dafür, wie dieses Token in Ihrem Node‑MCP‑Server „lebt“.
2. Grundlegende Keycloak-Entitäten im MCP-Kontext
Damit Sie sich später in der Admin‑Konsole nicht verirren, ordnen wir die Entitäten ein.
Realm: Bereich für Einstellungen und Benutzer
Ein Realm in Keycloak ist ein isolierter Bereich mit eigenen Benutzern, Clients und Richtlinien. Eine passende Analogie ist ein „gemietetes Büro im Business‑Center“: Jeder hat seine eigenen Räume, seine Mitarbeiterliste und eigene Zutrittsregeln.
Für den Kurs und Ihre erste echte App ist es sinnvoll, einen separaten Realm zu erstellen, zum Beispiel giftgenius-mcp oder mcp-course. Das ermöglicht:
- den master-Realm nicht anzufassen, um die Admin‑Konsole nicht versehentlich zu beschädigen;
- Einstellungen und Benutzer über verschiedene Umgebungen (dev / staging / prod) per Export/Import des Realms wiederzuverwenden.
Client: Eintrag über eine Anwendung (ChatGPT / MCP Jam)
Ein Client in Keycloak ist kein „Benutzer“, sondern eine Anwendung, die beim Auth‑Server Tokens abholt. In unserem Fall ist das nicht Ihr Next.js‑Backend, sondern der MCP‑Client: ChatGPT, MCP Jam, eventuell separat Ihr Widget, wenn Sie im UI einen manuellen OAuth‑Flow implementieren.
Schlüsselfelder des Clients:
- client_id – ein String‑Identifier;
- Typ (public / confidential / bearer-only);
- aktivierte OAuth‑Flows (Standard Flow, Client Credentials usw.);
- Liste erlaubter Redirect‑URIs;
- Liste der Scopes und Protocol Mappers (Claims im Token).
Für ChatGPT/MCP Jam benötigen wir einen public client, weil:
- ChatGPT als Client kein client_secret sicher speichern kann;
- MCP Jam als Desktop-/Browser‑Tool ebenfalls in einer nicht vertrauenswürdigen Umgebung läuft.
User: eine reale Person
Ein User ist ein „echter“ Benutzer: Er hat Benutzername, Passwort, E‑Mail, Attribute, Gruppen, Rollen. Wenn sich jemand über Keycloak anmeldet, landen dessen sub und andere Daten im Token, das Sie anschließend auf dem MCP‑Server prüfen und auf Ihre accountId / tenantId abbilden.
Für unsere Demo genügt es völlig:
- ein bis zwei Testbenutzer (z. B. alice@example.com, bob@example.com);
- ggf. ein paar Attribute wie tenant oder plan, wenn wir zeigen wollen, wie Claims aus dem Token das Verhalten der Tools beeinflussen.
3. Auswahl des Client-Typs: public, PKCE und warum ohne Secret
Nun zum Kern: Wie konfigurieren wir den Client in Keycloak speziell für ChatGPT/MCP?
Public vs. Confidential: warum kein client_secret
In einer klassischen Web‑App bauen Sie ein Backend, legen dort das client_secret ab, und der Server holt die Tokens beim IdP. Das ist ein confidential client: Er darf ein Secret speichern.
In der ChatGPT‑Welt ist es umgekehrt:
- Der OAuth‑Client ist die Plattform ChatGPT selbst oder ein Tool wie MCP Jam;
- Sie kontrollieren dessen Code und Umgebung nicht;
- jedes client_secret, das Sie ChatGPT geben, gilt sofort als kompromittiert.
Daher arbeiten ChatGPT/Jam als public clients, also ohne client_secret, und gleichen das durch PKCE (Proof Key for Code Exchange) aus.
PKCE in einfachen Worten
PKCE ist ein einmaliges „Sitzungs‑Secret“. Ziel ist, dass man einen Authorization Code nicht einfach abfangen und von einem anderen Ort gegen ein Token eintauschen kann. Das läuft so ab:
- Der Client generiert eine zufällige Zeichenkette code_verifier.
- Er hasht sie (meist SHA‑256) und erhält code_challenge.
- Beim Redirect auf /authorize sendet er code_challenge und code_challenge_method=S256.
- Nach dem Login wird der Benutzer mit einem code zur Redirect‑URI zurückgeschickt.
- Der Client macht ein POST auf /token und übergibt code und den ursprünglichen code_verifier.
- Keycloak hasht den Verifier, vergleicht ihn mit dem Challenge und gibt bei Übereinstimmung ein Token aus.
Wichtig für uns: Das erledigen ChatGPT/MCP Jam für uns. Wir müssen im Keycloak‑Client lediglich Authorization Code + PKCE (S256) aktivieren und kein client_secret verlangen.
4. Schritt-für-Schritt-Konfiguration von Keycloak für das MCP-Szenario
Wir haben festgelegt: Für ChatGPT/MCP Jam brauchen wir einen public client mit Authorization Code Flow und PKCE (S256), ohne client_secret. Schauen wir uns an, wie diese Konfiguration in Keycloak aussieht.
Angenommen, Sie haben bereits einen laufenden Keycloak (Docker‑Container, lokale Installation – egal). Uns interessiert jetzt die Logik der Einstellungen, nicht die konkreten Klickpfade.
Einen neuen Realm für die App anlegen
Legen Sie den Realm giftgenius-mcp an: Das ist ein eigener Bereich, in dem es geben wird:
- Benutzer speziell für die ChatGPT‑Anwendung;
- Clients, über die ChatGPT/MCP Jam OAuth durchläuft;
- eigene Passwort‑ und Token‑Richtlinien.
Praxis‑Tipp: Vermischen Sie nicht den Realm, in dem Sie Mitarbeiter für die Admin‑Konsole autorisieren, mit dem Realm für ChatGPT‑Clients. Das ist sicherer und logisch einfacher.
Testbenutzer anlegen
Legen wir einen Benutzer an, zum Beispiel alice:
- username: alice;
- email: alice@example.com;
- ein Passwort setzen (zur Vereinfachung ohne strenge Richtlinien);
- optional das Attribut tenantId=demo-tenant oder die Rolle ROLE_PREMIUM hinzufügen.
Später können Sie im MCP‑Server das Token dekodieren, sub, email, tenantId auslesen und mit Ihrem Benutzermodell verknüpfen.
Public Client für MCP Jam / ChatGPT anlegen
Jetzt kommt das Spannendste – der Client.
Auf konzeptioneller Ebene sollten die Parameter so aussehen:
- Client ID: giftgenius-mcp-client (der Name ist frei wählbar);
- Typ: public / Client Authentication off;
- Standard Flow (Authorization Code) aktiviert;
- PKCE mit Methode S256 aktiviert;
- Redirect‑URIs konfiguriert;
- benötigte Scopes konfiguriert (openid + Ihr Custom‑Scope, z. B. mcp:tools).
Standard Flow und PKCE aktivieren
Begrifflich bedeutet das:
- Authorization Code Flow aktivieren (oft „Standard Flow Enabled“ genannt);
- im PKCE‑Abschnitt pkceRequired=true setzen und meist explizit code_challenge_method=S256 angeben.
Warum S256: In aktueller OAuth‑2.1‑Dokumentation und in Empfehlungen von OpenAI/Model Context Protocol wird explizit S256 als sichere Methode unterstützt; plain‑PKCE gilt als unsicher.
Redirect-URI – die heikelste Stelle
Redirect‑URIs müssen zeichengetreu mit dem übereinstimmen, was der Client verwendet. Andernfalls erhalten Sie den Fehler invalid_redirect_uri beim Autorisieren.
In unserem Kurs gibt es zwei typische Clients:
- MCP Jam/Inspector zum Debuggen. Sie laufen üblicherweise auf http://localhost:PORT/.... Für lokale Szenarien ist es sinnvoll, Redirects dieser Form zu erlauben:
- http://localhost:5173/* oder den konkreten Pfad, den Jam benutzt.
- ChatGPT / Apps SDK in Produktion. Hier definiert die Plattform selbst die Redirect‑URI. In einer echten Integration schauen Sie in die aktuelle OpenAI‑Dokumentation und tragen den URL ein, den ChatGPT als Callback verwendet.
Für die Vorlesung ist das Verständnis wichtig: ChatGPT kann nicht einfach irgendeinen Redirect verwenden, er muss mit dem im Auth‑Server eingetragenen übereinstimmen. Daher:
- setzen Sie niemals * nach dem Motto „jeder URL ist okay“;
- für lokale Entwicklung sind Wildcards innerhalb von localhost vertretbar, aber nicht für Produktion.
Scopes: so wenig wie möglich, aber ausreichend
Scopes sind die kleine Rechte‑Liste, die der Client anfordert.
Für unser MCP‑Szenario braucht es meist:
- openid – um OpenID Connect zu aktivieren und ein id_token mit sub, teils email, zu erhalten;
- einen Custom‑Scope, z. B. mcp:tools, der anzeigt: „Zugriff auf MCP‑Tools erlaubt“.
In Keycloak geht das über Client Scopes:
- openid beibehalten;
- überflüssige Standard‑Scopes wie profile und email standardmäßig deaktivieren, wenn sie nicht benötigt werden;
- einen neuen Scope mcp:tools hinzufügen, mit dem Sie später den Zugriff auf Tools am Resource‑Server begrenzen.
Das ist aus zwei Gründen wichtig:
- Ohne openid erhalten Sie kein id_token und einige Standard‑OIDC‑Felder.
- Ohne einen separaten Custom‑Scope können Sie serverseitig nicht klar sagen: „Dieses Token darf meine Tools aufrufen.“
5. Token-Konfiguration: Lebensdauer, Signatur und Claims
Sehen wir uns an, welche Tokens Keycloak ausgibt und wie wir sie fürs MCP‑Szenario konfigurieren.
Lebensdauer des Access Tokens
In den Realm‑Einstellungen gibt es bei Keycloak den Bereich Tokens, in dem Sie festlegen können:
- Access Token Lifespan;
- Refresh Token Lifespan und andere Timeouts.
Für die ChatGPT‑App sind kurzlebige Access Tokens wichtig:
- einige Minuten bis Stunden sind ein vernünftiger Wert;
- wenn das Token abläuft, antwortet der MCP‑Server mit 401, ChatGPT startet den OAuth‑Flow erneut, der Benutzer meldet sich bei Bedarf wieder an.
Die Idee entspricht der OpenAI‑Dokumentation zum Apps SDK: kurze TTL + Token‑Erneuerung und die Möglichkeit, einen Benutzer relativ schnell „auszuloggen“, indem das Token beim IdP widerrufen wird.
Refresh Tokens sind für den ChatGPT‑Client in der Regel entweder weniger entscheidend oder werden mit kurzer Gültigkeit ausgegeben, um keine ewigen Sitzungen zu halten.
Welche Claims wir im Token sehen wollen
Minimal benötigen wir:
- sub – die eindeutige Benutzer‑ID in Keycloak;
- iss – wer das Token ausgestellt hat (Issuer);
- aud – für welche Ressource das Token bestimmt ist (wird im MCP‑Server verwendet);
- exp – Ablaufzeit;
- scope – Liste der Scopes.
Zusätzlich oft nützlich:
- email – falls Sie die E‑Mail‑Adresse sehen möchten;
- tenantId oder ein ähnlicher Claim – für Multitenancy‑Szenarien;
- roles – für zusätzliche Autorisierung.
In Keycloak wird das über Protocol Mappers konfiguriert:
- Standard‑Mapper für email, preferred_username etc.;
- Custom‑Mapper für Benutzerattribute (user.attribute → claim.name).
Beispiel: Ein Mapper, der die E‑Mail als Claim ins Token schreibt, indem er user.attribute=email, claim.name=email setzt.
Auf der MCP‑Server‑Seite können Sie diese Claims aus dem geparsten JWT entnehmen und:
- sub mit Ihrer accountId verknüpfen;
- tenantId verwenden, um nur die zu diesem Tenant gehörenden Daten zu laden;
- roles nutzen, um feinere Berechtigungen durchzusetzen.
Tokensignatur und JWKS
Keycloak signiert Access/ID‑Tokens standardmäßig mit einem asymmetrischen Algorithmus (typisch RS256) und veröffentlicht die öffentlichen Schlüssel über den JWKS‑Endpoint aus dem OpenID‑Discovery‑Dokument.
Für uns ist das wichtig, weil der MCP‑Server so:
- den issuer aus dem Token entnehmen kann;
- über /.well-known/openid-configuration den JWKS‑Endpoint findet;
- den öffentlichen Schlüssel beziehen und die Tokensignatur lokal prüfen kann.
Diesen Teil vertiefen wir in der Vorlesung zum MCP‑Server als geschützter Ressource‑Server, aber es ist hilfreich zu verstehen, warum Keycloak diese Metadaten bereitstellt.
6. Dynamic Client Registration (DCR): wann sie sinnvoll ist
Dieser Abschnitt ist eher fortgeschritten. Bisher haben wir den Client „per Hand“ in der Admin‑Konsole konfiguriert – völlig ausreichend, um die App zu starten. Der OAuth‑Standard erlaubt es Clients jedoch, sich dynamisch über einen separaten Endpoint zu registrieren.
Im Kontext von ChatGPT und MCP weist OpenAI ausdrücklich darauf hin, dass die Plattform Dynamic Client Registration verwenden kann. Das heißt, ChatGPT registriert sich „on the fly“ beim Auth‑Server über den registration_endpoint aus dem Discovery‑Dokument.
In Keycloak sieht das so aus:
- DCR wird auf Realm‑Ebene aktiviert;
- Sie konfigurieren die Richtlinie: Wer neue Clients registrieren darf und mit welchen Grant Types/Scopes.
Ein Beispiel‑JSON für die Registrierung eines Public Clients mit Authorization Code + PKCE und dem Scope openid mcp:tools kann so aussehen:
{
"clientName": "My ChatGPT App",
"redirectUris": ["https://jam.proxy.mcpapps.com/callback"],
"grantTypes": ["authorization_code"],
"responseTypes": ["code"],
"scope": "openid mcp:tools",
"tokenEndpointAuthMethod": "none"
}
Dabei bedeutet tokenEndpointAuthMethod: "none" genau den Public‑Client ohne client_secret.
Für den Kurs genügt es zu wissen, dass:
- DCR nützlich ist, wenn es viele oder kurzlebige Clients gibt;
- ChatGPT sich potenziell selbst in Ihrem IdP registrieren kann;
- Sie anfangs aber gut mit einem statischen, per UI angelegten Client auskommen.
7. Wie das mit unserer Übungsanwendung zusammenhängt
Zur Erinnerung: Wir haben einen Übungs‑MCP‑Server (z. B. GiftGenius), der Folgendes kann:
- eine Liste möglicher Geschenke ausgeben;
- Wunschlisten des Benutzers speichern;
- später – den Commerce‑Teil ansprechen, Bestellungen auslösen usw.
Solange der MCP‑Server offen ist, weiß er nicht, wer ihn anfragt:
- Eine Anfrage von ChatGPT kann logisch „von Alice“ oder „von Bob“ stammen, aber der MCP‑Server unterscheidet das nicht;
- Sie können keine private Geschenk‑Historie anzeigen;
- Sie können nicht sicher vom richtigen Account abbuchen.
Nach der Konfiguration von Keycloak als Auth‑Server ändert sich die Lage:
- ChatGPT erkennt über .well-known Ihres MCP‑Ressourcenservers, dass er geschützt ist und ein Token verlangt.
- ChatGPT schickt den Benutzer per Authorization Code + PKCE Flow zu Keycloak.
- Der Benutzer meldet sich an (unsere alice).
- ChatGPT erhält ein Access Token, das sub, email, mcp:tools und weitere Claims enthält.
- ChatGPT ruft das GiftGenius‑Tool nun mit Authorization: Bearer <token> auf.
- Der MCP‑Server prüft das Token und erkennt: „Aha, das ist Alice mit sub= … und tenantId=demo-tenant“ – und antwortet entsprechend.
Diese Kette schließen wir in der nächsten Vorlesung ab, in der wir den MCP‑Server zu einem „echten“ Resource‑Server machen: Metadaten‑Endpoint, Token‑Prüfung und Benutzerzuordnung.
8. Kleine Praxisbeispiele (unser Stack: TypeScript + Node)
Alles Folgende ist kein „einzig richtiger“ Weg, sondern eine Referenz, wie das in einem typischen Node/TypeScript‑Stack aussehen kann. Wenn Sie gerade mehr mit dem Klicken in Keycloak beschäftigt sind, können Sie diesen Abschnitt überfliegen und zurückkommen, wenn Sie den MCP‑Server anbinden.
Zwar wird Keycloak meist über die UI oder sein Admin‑REST‑API konfiguriert, aber ein paar Code‑Snippets darum herum sind hilfreich, um zu sehen, wie Sie das serverseitig im MCP‑Server nutzen.
Nehmen wir an, wir haben bereits einen Node.js‑MCP‑Server (TypeScript) auf Basis des offiziellen SDK.
Auth-Konfiguration (Issuer und Audience)
Erstellen wir ein kleines Modul authConfig.ts:
// authConfig.ts
export const authConfig = {
issuer: 'https://auth.my-company.com/realms/giftgenius-mcp',
audience: 'https://mcp.my-company.com', // URL Ihres MCP-Servers
requiredScopes: ['mcp:tools'], // Mindestumfang, den wir im Token erwarten
};
Hier ist issuer die URL Ihres Keycloak‑Realms, audience der Ressourcen‑Identifier (den verwenden wir noch in der Token‑Konfiguration und im MCP).
Basisvalidierung eines JWT via JWKS
In der Praxis verwenden Sie typischerweise Bibliotheken wie jsonwebtoken + jwks-rsa oder fertige Utilities aus dem MCP‑SDK. Ein einfaches Skeleton könnte so aussehen:
// verifyToken.ts
import jwt from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';
import { authConfig } from './authConfig';
const client = jwksClient({
jwksUri: `${authConfig.issuer}/protocol/openid-connect/certs`,
});
function getKey(header: any, callback: any) {
client.getSigningKey(header.kid, (err, key) => {
const signingKey = key?.getPublicKey();
callback(err, signingKey);
});
}
export function verifyAccessToken(token: string): Promise<any> {
return new Promise((resolve, reject) => {
jwt.verify(
token,
getKey,
{
audience: authConfig.audience,
issuer: authConfig.issuer,
},
(err, decoded) => (err ? reject(err) : resolve(decoded)),
);
});
}
Natürlich sollten Fehlerbehandlung und Caching der Schlüssel sorgfältiger erfolgen, aber die Idee ist klar: Keycloak veröffentlicht JWKS‑Schlüssel, wir holen sie und prüfen die Signatur.
Scope-Prüfung und Extraktion der Identität
In Middleware für MCP‑Tools können Sie so etwas tun:
// authMiddleware.ts
import { verifyAccessToken } from './verifyToken';
import { authConfig } from './authConfig';
export async function requireAuth(bearerToken: string) {
const token = bearerToken.replace(/^Bearer\s+/i, '');
const decoded: any = await verifyAccessToken(token);
const scopes = (decoded.scope as string).split(' ');
const hasScope = authConfig.requiredScopes.every(s => scopes.includes(s));
if (!hasScope) {
throw new Error('Insufficient scope');
}
return {
userId: decoded.sub,
email: decoded.email,
tenantId: decoded.tenantId,
};
}
Und in den Handlern der MCP‑Tools nutzen Sie dann userId und tenantId, um die passenden Wunschlisten des Benutzers zu laden. Die Tools selbst haben wir in früheren Modulen implementiert – hier geht es darum zu sehen, wie das Keycloak‑Token zu einer für Ihr Backend verwertbaren Identität wird.
9. Häufige Fehler bei der Konfiguration von Keycloak als MCP-Auth-Server
Fehler Nr. 1: Verwendung eines Confidential Clients mit client_secret.
Aus Gewohnheit wird manchmal ein Client vom Typ confidential erstellt und versucht, das client_secret in der MCP/ChatGPT‑Konfiguration zu hinterlegen. In der ChatGPT‑App‑Ökosphäre soll und wird das nicht sicher funktionieren: ChatGPT ist ein public client und kann kein Secret speichern. Der richtige Weg ist public client + PKCE.
Fehler Nr. 2: Zu breite Scopes standardmäßig aktiviert.
Die Scopes profile, email und eine ganze Reihe weiterer Standard‑Scopes aktiviert zu lassen und dann solche Tokens an jeden Chat zu verteilen, ist keine gute Idee. Besser minimieren: openid und ein konkreter mcp:tools (oder ein paar anwendungsspezifische Scopes) reichen für die ersten Versionen. Das verringert das Risiko unnötiger Datenabflüsse und macht das Verhalten vorhersehbarer.
Fehler Nr. 3: Falsche Redirect‑URI.
Klassiker: In Keycloak steht http://localhost:5173/callback, aber MCP Jam nutzt http://localhost:5173/. Oder umgekehrt. Ergebnis: invalid_redirect_uri und frustrierendes Debugging. Prüfen Sie immer den genauen Redirect‑Wert in der Jam/ChatGPT‑Dokumentation und tragen Sie ihn zeichengetreu ein.
Fehler Nr. 4: PKCE nicht aktiviert oder mit falscher Methode.
Manche Keycloak‑Versionen erfordern, „PKCE required“ separat zu aktivieren und die Methode S256 festzulegen. Wenn das fehlt, kann ChatGPT/Jam, das PKCE erwartet, mit invalid_request auf code_challenge meckern. Prüfen Sie die PKCE‑Einstellungen für Public‑Clients unbedingt.
Fehler Nr. 5: Falsche oder fehlende Claims im Token.
Mitunter fehlen sub oder email, weil der entsprechende Scope oder Protocol Mapper nicht gesetzt ist. Folge: Auf dem MCP‑Server sehen Sie zwar ein Token, können es aber keinem realen Benutzer zuordnen. Lösung: Sicherstellen, dass die benötigten Felder (mindestens sub, besser auch email/tenantId) in Access/ID‑Tokens gemappt sind.
Fehler Nr. 6: Zu lange TTL für Access Tokens.
Aus Sicherheitssicht ist es keine gute Idee, Access Tokens für einen Tag/eine Woche auszustellen. Bei Token‑Leakage erhält ein Angreifer langfristigen Zugriff auf die MCP‑Ressource. Besser: kurzlebige Access Tokens (Minuten oder Stunden) und bei Bedarf erneute Autorisierung.
Fehler Nr. 7: Verwechslung von Realms und Nutzung von master.
Oft wird als Erstes der Client und die Benutzer direkt im Realm master angelegt. Dann kommen noch ein paar Projekte dazu – und am Ende ist unklar, was wohin gehört. Legen Sie besser gleich einen separaten Realm pro Anwendung/Kurs an. Das erleichtert Ihnen und Ihrem DevOps das Leben.
GO TO FULL VERSION