Flux MCP OAuth 2.1 expliqué : CIMD ou DCR, avec exemples
Le flux MCP OAuth 2.1 étape par étape, du défi 401 et des recherches de métadonnées jusqu’à PKCE et à la validation des jetons. Vrais JSON pour les Client ID Metadata Documents et l’enregistrement dynamique des clients, un tableau comparatif et les contrôles de sécurité que chaque approche exige.
Un client MCP qui veut appeler un serveur protégé se heurte à un problème de confiance dès sa toute première requête. Le serveur ne connaît pas ce client, et le serveur d’autorisation qui le gère ne le connaît pas non plus. La spécification d’autorisation MCP règle ce point avec OAuth 2.1, et l’élément qui a le plus changé ces douze derniers mois est la manière dont un client obtient son client_id. Client ID Metadata Documents (CIMD) est arrivé dans la révision du 2025-11-25 comme mécanisme d’enregistrement recommandé, et la révision du 2026-07-28 marque l’enregistrement dynamique des clients (DCR) comme déprécié. Cet article suit le flux MCP OAuth 2.1 depuis le premier 401 jusqu’au premier appel d’outil autorisé, montre de vraies requêtes et réponses pour les deux voies d’enregistrement, et se termine par une règle simple pour choisir entre elles.
Pourquoi MCP a besoin d’OAuth 2.1
Imaginez la réception d’un hôtel. Vous montrez votre pièce d’identité une seule fois, l’accueil confirme qui vous êtes, et vous repartez avec une carte de chambre qui ouvre votre chambre et rien d’autre. OAuth fonctionne de la même façon. Le serveur d’autorisation joue le rôle de la réception, le jeton d’accès celui de la carte de chambre, et le serveur MCP celui de la porte qui vérifie la carte. La porte ne voit jamais votre passeport, et une carte de la chambre 412 n’ouvre pas la chambre 518.
L’autorisation est facultative dans MCP, mais les règles deviennent strictes dès que vous l’activez. Les serveurs basés sur HTTP DEVRAIENT suivre la spécification d’autorisation, tandis que les serveurs stdio NE DEVRAIENT PAS la suivre et lisent les identifiants dans l’environnement. Voici ce que la spécification rend obligatoire :
PKCE avec la méthode S256. Les clients doivent aussi vérifier que le serveur d’autorisation annonce code_challenge_methods_supported, et refuser de continuer si ce champ est absent.
Métadonnées de ressource protégée (RFC 9728). Le serveur MCP les publie, et le client s’en sert pour trouver le bon serveur d’autorisation.
Indicateurs de ressource (RFC 8707). Les clients envoient un paramètre resource dans la requête d’autorisation comme dans la requête de jeton.
Jetons bearer dans un en-tête. L’en-tête Authorization: Bearer accompagne chaque requête HTTP, et les jetons n’apparaissent jamais dans la chaîne de requête.
Validation de l’audience. Un serveur MCP n’accepte que les jetons émis pour lui-même.
Les quatre acteurs
Chaque flux de cet article fait intervenir les mêmes quatre parties. Gardez-les bien en tête et le reste se lit facilement.
Acteur
Rôle OAuth
Exemple typique
Utilisateur
Propriétaire de la ressource
Une personne qui approuve l’accès dans un navigateur
Client MCP
Client OAuth
Une application de bureau d’IA, un IDE, un agent en ligne de commande
Serveur MCP
Serveur de ressources
https://mcp.example.com/mcp
Serveur d’autorisation
Émet les jetons
Auth0, Okta, Microsoft Entra ID, ou votre propre service
💡 Le serveur MCP et le serveur d’autorisation peuvent vivre dans un même déploiement ou appartenir à deux entreprises différentes. Un client ID n’a de sens que pour le serveur d’autorisation qui l’a émis ou accepté, donc un client ne doit jamais supposer qu’un identifiant fonctionne partout.
Le flux, du 401 au jeton
La poignée de main est une courte chaîne de requêtes HTTP ordinaires. Vous pouvez suivre chacune d’elles dans un terminal, ce qui rend le débogage bien moins mystérieux que ne le laissent penser les acronymes.
Le défi 401
Le client envoie une requête MCP sans jeton. Le serveur la refuse et indique au client où chercher :
Le paramètre scope est l’indication du serveur sur le minimum de droits nécessaires à cette requête. Si le paramètre resource_metadata est absent, le client se rabat sur des URL bien connues, d’abord /.well-known/oauth-protected-resource/mcp (avec le chemin inséré), puis la version à la racine.
Deux recherches de métadonnées
Le client récupère le document de métadonnées de ressource protégée et y lit quel serveur d’autorisation utiliser :
Il demande ensuite au serveur d’autorisation de se décrire, en essayant d’abord /.well-known/oauth-authorization-server, puis /.well-known/openid-configuration d’OpenID Connect. Le issuer de la réponse doit correspondre à l’URL utilisée par le client pour construire la requête, sinon le document est rejeté. Une réponse typique ressemble à ceci :
Deux champs de cette réponse déterminent la manière dont le client s’enregistre : client_id_metadata_document_supported (CIMD) et registration_endpoint (DCR). Nous y reviendrons tous les deux.
PKCE et le paramètre de ressource
Une fois le client_id en main, le client génère un vérificateur PKCE à usage unique, le hache, puis ouvre le navigateur. Il enregistre aussi le issuer attendu pour pouvoir le vérifier dans la réponse.
GET https://auth.example.com/authorize?response_type=code
&client_id=https%3A%2F%2Fapp.example.com%2Foauth%2Fclient-metadata.json
&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256
&resource=https%3A%2F%2Fmcp.example.com%2Fmcp
&scope=files%3Aread
&state=xyz123
La valeur resource est l’URI canonique du serveur MCP. Les clients DOIVENT l’envoyer même si le serveur d’autorisation l’ignore, car elle permet de lier un jeton à un serveur précis.
Échange du code et utilisation du jeton
L’utilisateur approuve, et le navigateur revient vers l’URI de redirection avec un code, le state et, idéalement, un paramètre iss. La révision du 2026-07-28 ajoute une vérification de l’émetteur issue de la RFC 9207 : si iss est présent, le client le compare à l’émetteur enregistré avant d’envoyer le code quelque part. Cela bloque les attaques de confusion, où un serveur d’autorisation malveillant tente de récupérer des codes destinés à un serveur légitime.
Vient ensuite la requête de jeton, qui prouve la possession du vérificateur PKCE :
La réponse contient le jeton d’accès, et à partir de là chaque requête vers le serveur MCP inclut Authorization: Bearer <access-token>. Si un scope manque plus tard au jeton, le serveur répond 403 avec error="insufficient_scope", et le client se réautorise avec l’union des anciens et des nouveaux scopes.
DCR : son fonctionnement, ses failles
L’enregistrement dynamique des clients vient de la RFC 7591. L’idée est simple : avant la première connexion, le client envoie ses informations à un registration_endpoint et reçoit en retour un client_id tout neuf. Personne ne remplit de formulaire. Pendant des années, c’est la principale façon d’intégrer automatiquement un client inconnu, ce qui explique que les premières versions de MCP l’aient adoptée.
Une requête d’enregistrement
Voici un échange réaliste pour un client MCP de bureau :
Remarquez application_type. Depuis la révision du 2026-07-28, les clients DOIVENT le renseigner. Les serveurs qui parlent OpenID Connect traitent une valeur absente comme web, et cette valeur par défaut peut rejeter des URI de redirection localhost.
Pourquoi les serveurs peinent à le gérer
Le DCR fonctionne, mais il fait peser une lourde charge sur le serveur d’autorisation :
Un point d’écriture public. N’importe qui sur Internet peut créer des enregistrements, il vous faut donc des limites de débit, une expiration et des tâches de nettoyage.
Un enregistrement par association. Chaque client s’enregistre séparément auprès de chaque serveur d’autorisation et doit stocker le résultat en toute sécurité, indexé par issuer. Lorsque le serveur d’autorisation change, le client doit s’enregistrer à nouveau.
Des noms que personne n’a vérifiés. L’écran de consentement affiche le client_name saisi par la personne qui s’enregistre, de sorte qu’une application hostile peut se faire passer pour n’importe quoi.
Croissance de la base de données. Des milliers d’installations d’un client populaire deviennent des milliers d’enregistrements qui signifient tous la même chose.
Ces coûts sont la raison pour laquelle la spécification oriente désormais les nouvelles implémentations ailleurs.
CIMD : l’URL est le client ID
Pensez à un passeport. Personne ne demande au douanier de mémoriser à l’avance qui vous êtes. Vous lui remettez un document, et il le vérifie auprès de l’autorité qui l’a délivré. CIMD inverse l’enregistrement de la même façon. Le client publie un document JSON à une URL HTTPS stable, et cette URL est le client_id. Le serveur d’autorisation lit le document la première fois qu’il voit l’URL, si bien qu’il n’y a rien à enregistrer à l’avance.
Le document de métadonnées
Les règles pour le client sont courtes. Le client_id doit utiliser https et comporter un chemin, le document doit contenir client_id, client_name et redirect_uris, et le client_id inscrit dans le fichier doit correspondre exactement, caractère pour caractère, à l’URL à partir de laquelle il a été servi. L’exemple de la spécification ressemble à ceci :
Lorsqu’une requête d’autorisation arrive avec une URL de la forme client_id, le serveur d’autorisation suit une routine fixe :
Récupérez le document avec une simple requête HTTPS GET.
Vérifiez qu’il s’agit d’un JSON valide et qu’il contient les champs obligatoires.
Vérifiez que le client_id du fichier est exactement identique à l’URL.
Vérifiez que le redirect_uri de la requête correspond à l’un de ceux listés dans le fichier.
Mettez le résultat en cache en respectant les en-têtes de cache HTTP.
Affichez à l’utilisateur le client_name et le nom d’hôte de redirection sur l’écran de consentement.
Un exemple minimal des étapes 1 à 3 en TypeScript, écrit pour la clarté plutôt que pour la production :
async function loadClient(clientId: string) {
const url = new URL(clientId);
if (url.protocol !== "https:" || url.pathname === "/") throw new Error("invalid_client");
await assertPublicHost(url.hostname); // reject private, loopback and link-local addresses
const res = await fetch(url, { redirect: "error", signal: AbortSignal.timeout(5000) });
const doc = await res.json();
if (doc.client_id !== clientId) throw new Error("invalid_client");
if (!doc.client_name || !Array.isArray(doc.redirect_uris)) throw new Error("invalid_client");
return doc;
}
Annoncer la prise en charge de CIMD
Le serveur d’autorisation annonce cette fonctionnalité dans ses métadonnées avec "client_id_metadata_document_supported": true. Les clients qui la trouvent utilisent leur URL comme client_id et sautent complètement l’enregistrement. Comme l’identifiant est une URL publique, il se transporte aussi très bien : le même client peut dialoguer demain avec un autre serveur d’autorisation sans s’enregistrer à nouveau.
CIMD et DCR côte à côte
Question
CIMD
DCR
Statut dans la spécification du 2026-07-28
Recommandé (DEVRAIT)
Déprécié, conservé pour la compatibilité (PEUT)
Qui stocke l’enregistrement du client
Le client l’héberge, le serveur le met en cache
Le serveur d’autorisation le stocke
Forme du client_id
Une URL telle que https://app.example.com/oauth/client-metadata.json
Une chaîne opaque telle que s6BhdRkqt3
Nécessite un point d’enregistrement
Non
Oui, annoncé sous registration_endpoint
Travail avant la première connexion
Aucun pour le client
Un POST par serveur d’autorisation
Portable d’un serveur d’autorisation à l’autre
Oui
Non, il faut s’enregistrer à nouveau par émetteur
Risque principal
SSRF pendant la récupération, usurpation de localhost
Abus d’un point ouvert, enregistrements parasites
Comment un serveur l’annonce
client_id_metadata_document_supported
registration_endpoint
Un client qui prend en charge toutes les options DEVRAIT choisir dans cet ordre :
Utiliser les informations d’un client préenregistré s’il en possède pour ce serveur.
Utiliser CIMD si le serveur d’autorisation annonce sa prise en charge.
Se rabattre sur DCR si un registration_endpoint existe.
Demander à l’utilisateur de saisir manuellement les informations du client.
💡 Le préenregistrement reste la meilleure option lorsque vous l’avez. Si vous contrôlez à la fois le client et le serveur d’autorisation, un client_id fixe évite toutes les recherches ci-dessus.
Les contrôles de sécurité à ne pas sauter
Passer du DCR au CIMD ne supprime pas les risques. Il les déplace vers d’autres endroits, et chacun d’eux a besoin d’un responsable.
SSRF lors de la récupération
Avec CIMD, un visiteur anonyme décide quelle URL votre serveur va demander. Si client_id pointe vers https://169.254.169.254/latest/meta-data/ ou vers un panneau d’administration interne, un récupérateur mal conçu devient un relais vers votre réseau. Résolvez d’abord le nom d’hôte et rejetez les plages privées, de loopback et de lien local. Fixez un délai court, limitez la taille de la réponse et soyez strict sur les redirections.
Redirections vers localhost
Un document de métadonnées ne peut pas prouver qu’un processus à l’écoute sur localhost:3000 appartient au client nommé dans le fichier. N’importe quel programme local peut revendiquer ce port. Le serveur d’autorisation DOIT donc afficher le nom d’hôte de redirection sur l’écran de consentement, DEVRAIT avertir lorsque chaque URI de redirection est localhost, et PEUT exiger une attestation supplémentaire pour un niveau de garantie plus élevé. Les URI de redirection elles-mêmes doivent correspondre exactement, jamais par préfixe ou par motif.
Audience et transmission de jetons
Le serveur MCP est la dernière barrière, et il doit vérifier la carte, pas seulement y jeter un coup d’œil. Validez la signature, l’expiration, les scopes et, surtout, l’audience. Un jeton émis pour un autre service doit être rejeté avec un 401. Si votre serveur MCP appelle une API en amont, il lui faut un jeton distinct pour cette API, émis par le serveur d’autorisation de cette API. Transmettre le jeton du client s’appelle la transmission de jetons, et la spécification l’interdit formellement.
Une courte liste de contrôle à épingler près de votre écran :
Servez chaque point d’autorisation en HTTPS, et n’autorisez que des redirections en HTTPS ou localhost.
Gardez des jetons d’accès à courte durée de vie, et faites tourner les jetons de rafraîchissement pour les clients publics.
Utilisez le paramètre state et vérifiez-le.
Validez iss lorsqu’il est présent, avant d’échanger le code.
Incluez dans un seul défi tous les scopes nécessaires à une opération, pour éviter que l’utilisateur ne passe par des écrans d’approbation répétés.
Choisir une stratégie
La décision est moins spectaculaire que le débat ne le laisse croire. Prenez d’abord en charge CIMD, gardez DCR comme solution transitoire, et soyez honnête sur le côté de la table où vous vous trouvez.
Si vous exploitez un serveur MCP
Publiez les métadonnées de ressource protégée dans tous les cas. Choisissez un serveur d’autorisation qui prend en charge CIMD et, si le vôtre ne le peut pas encore, laissez DCR activé avec des limites de débit et une expiration plutôt que de bloquer tous les clients. Placez un scope dans votre défi WWW-Authenticate, répondez avec 403 et insufficient_scope lorsqu’un jeton ne suffit pas, et vérifiez l’audience à chaque requête.
Si vous développez un client MCP
Hébergez votre document de métadonnées à une URL que vous conserverez pendant des années, car l’URL est votre identité. Lisez les métadonnées du serveur d’autorisation, puis choisissez une voie d’enregistrement dans votre code :
Lorsque vous vous rabattez sur DCR, conservez les identifiants associés au issuer, définissez application_type: "native" pour les applications de bureau et les CLI, et ne les réutilisez jamais avec un autre serveur d’autorisation.
Essayez sur PicassoIA
Quel que soit le côté de la poignée de main que vous construisez, vous écrirez beaucoup de JSON, de jeux de test et de documentation. Un grand modèle de langage performant vous fera gagner du temps, et PicassoIA en propose plusieurs. Voici une façon rapide d’utiliser Claude Sonnet 5 comme relecteur de votre document de métadonnées :
Collez votre client-metadata.json et les métadonnées du serveur d’autorisation de votre fournisseur.
Demandez une vérification par liste de contrôle : « Vérifie ce document par rapport aux règles CIMD : client_id égal à l’URL, https avec un chemin, champs obligatoires présents, redirect_uris exacts. Liste chaque échec. »
Demandez que le résultat soit présenté sous forme de tableau règle, résultat et correctif, pour pouvoir le coller facilement dans une pull request.
Pour un second avis, lancez le même prompt sur GPT 5.6 Sol ou une passe rapide avec Gemini 3.5 Flash, et comparez les échecs que chacun relève.
💡 Les modèles relisent bien les documents, mais ils ne remplacent pas un vrai test. Exécutez votre flux sur un serveur d’autorisation de préproduction avant de mettre en production.
La documentation a autant besoin d’images que de JSON. Une image d’en-tête, un fond de schéma ou une carte pour les réseaux sociaux rend un article sur OAuth bien plus facile à partager, et PicassoIA est conçu précisément pour cela. Les résultats photoréalistes viennent de prompts précis : indiquez l’objectif, la direction de la lumière et les textures de la scène. Essayez quelque chose comme « une réceptionniste d’hôtel glissant une carte de chambre sur un comptoir en marbre, objectif 50 mm, lumière douce de fenêtre venant de la droite, grain de film » et voyez ce qui en ressort. Les développeurs peuvent aussi accéder aux modèles d’image et de vidéo de PicassoIA via l’API PicassoIA et les connexions MCP, afin que le même prompt puisse être lancé depuis votre propre agent.
Prêt à créer vos propres visuels ? Ouvrez PicassoIA, choisissez un modèle et lancez votre premier prompt dès aujourd’hui.