1. Qu’est-ce qu’un serveur d’authentification dans la pratique et pourquoi choisir Keycloak
Commençons par un bref rappel : un Auth Server (IdP) est un service qui :
- affiche à l’utilisateur l’écran de connexion/inscription et le consentement ;
- émet des jetons OAuth/OIDC (access_token, id_token, refresh_token) ;
- publie le document de découverte et les clés JWKS afin que les serveurs de ressources puissent vérifier ces jetons.
Dans notre pile :
- ChatGPT / MCP Jam agit comme client OAuth (public client) ;
- votre serveur MCP — comme Resource Server ;
- Keycloak — comme Auth Server.
Pourquoi Keycloak est pratique pour le cours et la vraie vie :
- il est open source, facile à lancer localement/en Docker ;
- il propose un modèle d’entités assez clair : realm, clients, users, roles ;
- en substance, toute configuration apprise dans Keycloak se transpose presque 1:1 vers Auth0/Okta/Cognito : on y retrouve les mêmes concepts — client, scopes, redirect URIs, PKCE.
Idée importante : nous ne configurons pas « Keycloak pour tout le projet », mais un Realm spécifique pour notre ChatGPT App. C’est en quelque sorte un « bac à sable » d’authentification dédié aux clients MCP.
En bref, à la fin de ce cours vous aurez :
- votre propre realm dans Keycloak pour l’application ChatGPT ;
- un public‑client configuré avec Authorization Code + PKCE ;
- un ensemble minimal de scopes et de claims ;
- la compréhension de la façon dont ce jeton vit dans votre serveur Node‑MCP.
2. Les entités de base de Keycloak à travers le prisme MCP
Pour ne pas se perdre dans l’interface d’administration, clarifions les entités.
Realm : espace de configuration et d’utilisateurs
Un realm dans Keycloak est un espace isolé avec ses propres utilisateurs, clients et politiques. Une analogie utile — « un bureau loué dans un centre d’affaires » : chacun a son espace, sa liste d’employés et ses règles d’accès.
Pour ce cours et pour votre première application réelle, il est judicieux de créer un realm séparé, par exemple giftgenius-mcp ou mcp-course. Cela permet de :
- ne pas toucher au realm master pour éviter de casser l’admin ;
- réutiliser la configuration et les utilisateurs entre différents environnements (dev / staging / prod) via l’export/import du realm.
Client : entrée décrivant l’application (ChatGPT / MCP Jam)
Un client dans Keycloak n’est pas un « utilisateur », mais une application qui demande des jetons à l’Auth Server. Dans notre cas, ce n’est pas votre backend Next.js, mais bien le client MCP : ChatGPT, MCP Jam, et éventuellement votre widget séparé si vous implémentez un flux OAuth manuel dans l’UI.
Champs clés du client :
- client_id — identifiant chaîne ;
- type (public / confidential / bearer-only) ;
- flux OAuth activés (Standard Flow, Client Credentials, etc.) ;
- liste des redirect URI autorisés ;
- liste des scopes et protocol mappers (claims dans le jeton).
Pour ChatGPT/MCP Jam, nous avons besoin d’un public client, parce que :
- ChatGPT en tant que client ne peut pas stocker en sécurité un client_secret ;
- MCP Jam, en tant qu’outil desktop/navigateur, évolue aussi dans un environnement non fiable.
User : une personne réelle
Un user est un utilisateur « vivant » : il a un username, un mot de passe, un email, des attributs, des groupes, des rôles. Quand quelqu’un se connecte via Keycloak, c’est son sub et d’autres données qui figurent dans le jeton, que vous vérifiez ensuite sur le serveur MCP et mappez sur vos accountId / tenantId.
Pour notre démo, il suffit :
- d’un ou deux utilisateurs de test (par exemple, alice@example.com, bob@example.com) ;
- éventuellement — de quelques attributs comme tenant ou plan, si vous voulez montrer comment les claims du jeton influencent le comportement des tools.
3. Choisir le type de client : public, PKCE et pourquoi sans secret
Passons à l’essentiel : comment configurer exactement le client dans Keycloak pour ChatGPT/MCP.
Public vs Confidential : pourquoi pas de client_secret
Dans une application web classique, vous avez un backend, vous y stockez un client_secret, et c’est le serveur qui s’adresse à l’IdP pour obtenir des jetons. C’est un confidential client : il peut conserver un secret.
Dans l’univers de ChatGPT, c’est l’inverse :
- le client OAuth est la plateforme ChatGPT elle‑même ou un utilitaire comme MCP Jam ;
- vous ne contrôlez ni son code ni son environnement ;
- tout client_secret que vous donneriez à ChatGPT doit être considéré comme immédiatement compromis.
C’est pourquoi ChatGPT/Jam fonctionnent comme des public clients, c’est‑à‑dire sans client_secret, et compensent cela par PKCE — Proof Key for Code Exchange.
PKCE en termes simples
PKCE est un « secret à usage unique par session ». Son but est d’empêcher qu’on intercepte un authorization code pour l’échanger contre un jeton depuis un autre endroit. Le schéma est le suivant :
- Le client génère une chaîne aléatoire code_verifier.
- Il la hache (généralement SHA‑256) et obtient un code_challenge.
- Lors de la redirection vers /authorize, il envoie code_challenge et code_challenge_method=S256.
- Après connexion, l’utilisateur revient avec un code sur le redirect URI.
- Le client fait un POST sur /token, en transmettant le code et le code_verifier original.
- Keycloak hache le verifier, le compare au challenge et, si tout est correct, émet un jeton.
Ce qui est important pour nous : ChatGPT/MCP Jam gèrent tout cela pour nous. Nous devons simplement activer, côté client dans Keycloak, la prise en charge de l’Authorization Code + PKCE (S256) et ne pas exiger de client_secret.
4. Configuration pas à pas de Keycloak pour le scénario MCP
Nous avons décidé que pour ChatGPT/MCP Jam il nous faut un public client avec Authorization Code Flow et PKCE (S256), sans client_secret. Voyons maintenant à quoi ressemble cette configuration dans Keycloak.
Supposons que vous ayez déjà un Keycloak opérationnel (conteneur Docker, installation locale — peu importe). Nous nous concentrons ici sur la logique des réglages, pas sur l’endroit exact où cliquer.
Créer un nouveau realm pour l’application
Créons le realm giftgenius-mcp : c’est une zone séparée qui contiendra :
- des utilisateurs dédiés à l’application ChatGPT ;
- des clients via lesquels ChatGPT/MCP Jam passera l’OAuth ;
- ses propres politiques de mots de passe et de jetons.
Conseil pratique : ne mélangez pas le realm où vous autorisez les employés de l’admin avec le realm pour les clients ChatGPT. C’est à la fois plus sûr et plus simple sur le plan logique.
Ajouter un utilisateur de test
Créons un utilisateur, disons alice :
- username : alice ;
- email : alice@example.com ;
- définir un mot de passe (pour simplifier — sans politiques complexes) ;
- si besoin, ajouter l’attribut tenantId=demo-tenant ou le rôle ROLE_PREMIUM.
Plus tard, dans le serveur MCP, vous pourrez décoder le jeton, extraire sub, email, tenantId et les relier à votre modèle d’utilisateur.
Créer un public client pour MCP Jam / ChatGPT
Passons maintenant au Client.
Conceptuellement, les paramètres devraient ressembler à ceci :
- Client ID : giftgenius-mcp-client (nom au choix) ;
- Type : public / Client Authentication off ;
- Standard Flow (Authorization Code) activé ;
- PKCE activé avec la méthode S256 ;
- redirect URI configurés ;
- scopes nécessaires configurés (openid + votre scope personnalisé, par exemple mcp:tools).
Activer Standard Flow et PKCE
Au niveau des concepts :
- activer l’Authorization Code Flow (souvent appelé « Standard Flow Enabled ») ;
- dans la section PKCE, définir pkceRequired=true et le plus souvent explicitement code_challenge_method=S256.
Pourquoi S256 : dans la documentation moderne OAuth 2.1 et les recommandations OpenAI/Model Context Protocol, S256 est la méthode sûre prise en charge ; le plain‑PKCE est considéré comme peu sûr.
Redirect URI — le point le plus fragile
Les redirect URI doivent correspondre caractère pour caractère à ce que le client utilisera. Sinon, on obtient une erreur invalid_redirect_uri lors de l’autorisation.
Dans notre cours, il y a deux clients typiques :
- MCP Jam/Inspector pour le débogage. Ils tournent généralement sur http://localhost:PORT/.... Pour un scénario local, il est logique d’autoriser un redirect du type :
- http://localhost:5173/* ou un chemin spécifique utilisé par Jam.
- ChatGPT / Apps SDK en production. Ici, le redirect URI est défini par la plateforme elle‑même. Dans une intégration réelle, vous consulterez la documentation OpenAI à jour et indiquerez l’URL nécessaire que ChatGPT utilisera comme callback.
Dans le cadre du cours, l’important est de comprendre que ChatGPT ne peut pas utiliser un redirect arbitraire ; il doit correspondre à celui enregistré dans l’Auth Server. Par conséquent :
- ne mettez jamais * et « n’importe quelle URL » ;
- pour le développement local, des wildcards sont acceptables dans le cadre de localhost, mais pas en production.
Scopes : le minimum suffisant
Les scopes sont une petite liste de droits demandés par le client.
Pour notre scénario MCP, il faut le plus souvent :
- openid — pour activer OpenID Connect et recevoir un id_token avec le champ sub, parfois email ;
- un scope personnalisé, par exemple mcp:tools, signifiant « accès autorisé aux outils MCP ».
Dans Keycloak, cela se fait via les Client Scopes :
- laisser openid ;
- désactiver par défaut les scopes superflus comme profile et email, s’ils ne sont pas nécessaires ;
- ajouter un nouveau scope mcp:tools, avec lequel vous limiterez ensuite l’accès aux tools sur le Resource Server.
C’est important pour deux raisons :
- Sans openid, vous ne recevrez pas d’id_token ni une partie des champs OIDC standard.
- Sans scope personnalisé séparé, vous ne pourrez pas, côté serveur MCP, indiquer clairement : « ce jeton peut être utilisé pour appeler mes outils ».
5. Paramétrer les tokens : durée de vie, signature et claims
Voyons maintenant quels jetons Keycloak émettra et comment les adapter au scénario MCP.
Durée de vie de l’access token
Dans les paramètres du realm de Keycloak, il existe une section Tokens où vous pouvez définir :
- Access Token Lifespan ;
- Refresh Token Lifespan et d’autres timeouts.
Pour une application ChatGPT, des access tokens de courte durée sont importants :
- quelques minutes ou heures — c’est une valeur normale ;
- si le jeton a expiré, le serveur MCP répond 401, ChatGPT relance le flux OAuth et l’utilisateur se reconnecte si nécessaire.
L’idée est la même que dans la documentation OpenAI sur l’Apps SDK : TTL court + renouvellement des jetons et possibilité de « déconnecter » assez vite un utilisateur en révoquant le jeton côté IdP.
Les refresh tokens pour le client ChatGPT ne sont généralement pas critiques, ou sont émis avec une durée limitée pour éviter des sessions perpétuelles.
Quels claims voulons-nous voir dans le token
Le strict minimum dont nous avons besoin :
- sub — identifiant unique de l’utilisateur dans Keycloak ;
- iss — qui a émis le jeton (issuer) ;
- aud — pour quelle ressource est le jeton (utilisé ensuite sur le serveur MCP) ;
- exp — l’heure d’expiration ;
- scope — la liste des scopes.
En complément, sont souvent utiles :
- email — si vous souhaitez voir l’adresse de l’utilisateur ;
- tenantId ou un claim similaire — pour les scénarios multi‑tenant ;
- roles — pour une autorisation plus fine.
Dans Keycloak, cela se configure via des Protocol Mappers :
- des mappers standards pour email, preferred_username, etc. ;
- des mappers personnalisés pour les attributs utilisateur (user.attribute → claim.name).
Exemple : un mapper qui ajoute l’email comme claim dans le jeton en indiquant user.attribute=email, claim.name=email.
Côté serveur MCP, vous pourrez extraire ces claims du JWT parsé et :
- relier le sub à votre accountId ;
- utiliser le tenantId pour ne récupérer que les données appartenant à ce tenant ;
- utiliser les roles pour un contrôle d’accès plus fin.
Signature du token et JWKS
Par défaut, Keycloak signe les access/id tokens avec un algorithme asymétrique (généralement RS256) et publie les clés publiques via l’endpoint JWKS du document de découverte OpenID.
Pour nous, c’est important car le serveur MCP pourra :
- prendre l’issuer depuis le jeton ;
- via /.well-known/openid-configuration, trouver l’endpoint JWKS ;
- récupérer la clé publique et vérifier localement la signature du jeton.
Nous détaillerons cette partie dans le cours sur le serveur MCP en tant que ressource protégée, mais il est déjà utile de comprendre pourquoi Keycloak fournit ces métadonnées.
6. Dynamic Client Registration (DCR) : quand en a‑t‑on besoin
Cette section est plutôt avancée. Jusqu’ici, nous avons configuré le client « à la main » dans l’admin, ce qui est amplement suffisant pour lancer l’app. Mais le protocole OAuth permet aux clients de s’enregistrer dynamiquement via un endpoint dédié.
Dans le contexte de ChatGPT et MCP, OpenAI indique explicitement que la plateforme peut utiliser Dynamic Client Registration. Autrement dit, ChatGPT s’enregistre lui‑même sur l’Auth Server « à la volée » via le registration_endpoint du document de découverte.
Dans Keycloak, cela ressemble à ceci :
- on active la DCR au niveau du realm ;
- vous définissez une politique : qui peut enregistrer de nouveaux clients et avec quels grant types/scopes.
Un exemple de JSON pour enregistrer un public client avec Authorization Code + PKCE et le scope openid mcp:tools peut ressembler à ceci :
{
"clientName": "My ChatGPT App",
"redirectUris": ["https://jam.proxy.mcpapps.com/callback"],
"grantTypes": ["authorization_code"],
"responseTypes": ["code"],
"scope": "openid mcp:tools",
"tokenEndpointAuthMethod": "none"
}
Où tokenEndpointAuthMethod: "none" indique précisément un public client sans client_secret.
Pour le cours, il suffit de savoir que :
- la DCR est utile si les clients sont nombreux ou de courte durée de vie ;
- ChatGPT peut potentiellement s’enregistrer lui‑même dans votre IdP ;
- mais au début, on peut se contenter d’un client statique créé via l’UI.
7. Comment cela se connecte à notre application d’apprentissage
Rappelons que nous avons un serveur MCP d’apprentissage (par exemple, GiftGenius) qui sait :
- proposer une liste de cadeaux possibles ;
- stocker certaines listes de souhaits de l’utilisateur ;
- plus tard — appeler la partie commerce, passer des commandes, etc.
Tant que le serveur MCP est ouvert, il ne sait pas qui l’appelle :
- une requête de ChatGPT peut être logiquement « d’Alice » ou « de Bob », mais le serveur MCP ne le distingue pas ;
- vous ne pouvez pas afficher l’historique de cadeaux privé ;
- vous ne pouvez pas débiter en toute confiance le bon compte.
Après avoir configuré Keycloak comme Auth Server, la situation change :
- ChatGPT comprend, via le .well-known de votre ressource MCP, qu’elle est protégée et nécessite un jeton.
- ChatGPT envoie l’utilisateur vers Keycloak via le flux Authorization Code + PKCE.
- L’utilisateur se connecte (notre alice).
- ChatGPT obtient un access token contenant sub, email, mcp:tools et d’autres claims.
- ChatGPT appelle l’outil GiftGenius avec Authorization: Bearer <token>.
- Le serveur MCP, en vérifiant le jeton, comprend : « D’accord, c’est Alice avec sub=... et tenantId=demo-tenant » — et répond en conséquence.
Ce chaînage sera finalisé dans le cours suivant, où nous ferons du serveur MCP un « vrai » resource server : nous implémenterons un endpoint de métadonnées, la vérification du jeton et l’association à l’utilisateur.
8. Quelques petits exemples pratiques (notre stack : TypeScript + Node)
Tout ce qui suit n’est pas « la seule bonne » manière, mais une référence de ce à quoi cela peut ressembler dans un stack Node/TypeScript typique. Si vous êtes surtout concentré en ce moment sur le paramétrage de Keycloak, vous pouvez parcourir cette section et y revenir lors du branchement du serveur MCP.
Bien que la configuration de Keycloak se fasse principalement en « clics » via l’UI ou via son Admin REST API, il est utile de montrer quelques extraits de code autour pour comprendre comment vous utiliserez tout cela côté serveur MCP.
Supposons que nous ayons déjà un serveur MCP en Node.js (TypeScript) basé sur le SDK officiel.
Configuration d’autorisation (issuer et audience)
Créons un petit module authConfig.ts :
// authConfig.ts
export const authConfig = {
issuer: 'https://auth.my-company.com/realms/giftgenius-mcp',
audience: 'https://mcp.my-company.com', // URL de votre serveur MCP
requiredScopes: ['mcp:tools'], // minimum attendu dans le jeton
};
Ici issuer est l’URL du realm Keycloak, audience est l’identifiant de la ressource (nous l’utiliserons aussi dans la configuration du jeton et de MCP).
Vérification JWT de base via JWKS
En pratique, vous utiliserez probablement une bibliothèque comme jsonwebtoken + jwks-rsa ou des utilitaires prêts à l’emploi du SDK MCP. Un squelette minimal peut ressembler à ceci :
// 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)),
);
});
}
Bien sûr, la gestion des erreurs et la mise en cache des clés doivent être plus soignées, mais vous voyez l’idée : Keycloak publie les clés JWKS, nous les récupérons et vérifions la signature.
Vérifier le scope et extraire l’identité
Dans un middleware pour les MCP‑tools, vous pouvez faire quelque chose comme :
// 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,
};
}
Ensuite, dans les handlers de vos outils MCP, vous utiliserez userId et tenantId pour charger les listes de cadeaux de l’utilisateur. Nous avons déjà implémenté les outils dans les modules précédents ; l’important ici est de voir comment le jeton de Keycloak devient une identité compréhensible par votre backend.
9. Erreurs courantes lors de la configuration de Keycloak comme MCP Auth Server
Erreur n°1 : utilisation d’un confidential client avec client_secret.
Par habitude, on crée parfois un client de type confidential et on essaie de renseigner un client_secret dans la configuration MCP/ChatGPT. Dans l’écosystème ChatGPT App, cela ne devrait pas fonctionner et ne sera pas sécurisé : ChatGPT est un public client, il ne peut pas conserver un secret. La bonne approche — public client + PKCE.
Erreur n°2 : des scopes par défaut trop larges.
Laisser activés profile, email et une foule d’autres scopes standard — puis distribuer de tels jetons à chaque chat — n’est pas une bonne idée. Mieux vaut minimiser : openid et un mcp:tools spécifique (ou quelques scopes applicatifs) suffisent pour les premières versions. Cela réduit le risque de fuite de données inutiles et rend le comportement plus prévisible.
Erreur n°3 : redirect URI incorrect.
Grand classique : dans Keycloak, on indique http://localhost:5173/callback, alors que MCP Jam utilise http://localhost:5173/. Ou l’inverse. Résultat — invalid_redirect_uri et un débogage très frustrant. Vérifiez toujours la valeur exacte du redirect URI dans la documentation de Jam/ChatGPT et saisissez‑la au caractère près.
Erreur n°4 : PKCE non activé ou activé avec la mauvaise méthode.
Certaines versions de Keycloak exigent d’activer séparément « PKCE required » et d’indiquer la méthode S256. Si vous ne le faites pas, ChatGPT/Jam, qui s’attend à PKCE, peut recevoir une erreur invalid_request pointant code_challenge. Vérifiez impérativement les réglages PKCE pour les public clients.
Erreur n°5 : claims incorrects ou manquants dans le jeton.
Il arrive que le jeton n’ait pas sub ou email parce que le scope ou le protocol mapper correspondant n’est pas configuré. En conséquence, sur le serveur MCP, vous voyez le jeton mais vous ne pouvez pas le mapper à un utilisateur réel. Solution : assurez‑vous que les champs nécessaires (au minimum sub, et idéalement aussi email/tenantId) sont mappés dans les access/id tokens.
Erreur n°6 : TTL trop long pour les access tokens.
D’un point de vue sécurité, émettre des access tokens valables une journée/une semaine est une mauvaise idée. En cas de fuite, l’attaquant obtient un accès de longue durée à la ressource MCP. Mieux vaut des access tokens de courte durée (minutes ou heures) et ré‑autoriser si nécessaire.
Erreur n°7 : confusion avec les realms et utilisation de master.
Parfois, la première chose faite est de créer des clients et des utilisateurs directement dans le realm master. Puis on y accroche encore quelques projets — et au final, c’est le flou. Mieux vaut créer d’emblée un realm séparé pour chaque application/cours. Cela simplifiera la vie pour vous et votre DevOps.
GO TO FULL VERSION