U verbindt Claude Code met een MCP-server. De server vereist authenticatie. Uw klant stuurt een verzoek; de server antwoordt met een 401 Unauthorized en een WWW-Authenticate: Bearer-header met een resource_metadata-URL. Wat er vervolgens gebeurt, is een OAuth 2.1-stroom gedefinieerd door drie RFC’s en een reeks MCP-specifieke extensies die, samen genomen, een van de moeilijkere problemen op het gebied van agentbeveiliging oplossen: ervoor zorgen dat een token dat is verkregen om met deze MCP-server te praten, niet opnieuw kan worden afgespeeld tegen die.
Dit bericht schetst de volledige stroom: wat de specificatie zegt, wat de RFC’s feitelijk bieden en waar de valkuilen op het gebied van beveiliging zich in de praktijk verbergen.
De stroom: van 401 naar een resource-gebonden token
Het MCP-autorisatiemodel (revisie 2025-11-25, ongewijzigd overgenomen in de release candidate – bevroren 2026-05-21 – voor de definitieve specificatie gepland voor 2026-07-28) is een proces in twee fasen. Fase 1 is detectie: de 401 met WWW-Authenticate: Bearer resource_metadata="..." vertelt de client dat deze server OAuth-beveiligd is en waar de metadata ervan te vinden zijn. Fase 2 is geautoriseerde toegang: gebruik de metadata, ontdek de autorisatieserver, verkrijg een token dat is gebonden aan deze specifieke server en gebruik het.
De concrete stappen:
-
401 + WWW-authenticeren: de MCP-server wijst een niet-geverifieerd verzoek af. De parameter
resource_metadatain de uitdaging verwijst naar het Protected Resource Metadata-document van de server. -
PRM ophalen (RFC 9728): de client haalt de
/.well-known/oauth-protected-resource-URL op. Het antwoord is een JSON-document waarin de canonieke bron-URI van de server wordt aangegeven, de autorisatieserver(s) die deze beschermen, en de bereiken die de bron ondersteunt. De client valideert dat het veldresourcevan het document overeenkomt met de server die het wilde bereiken. Een verkeerde combinatie is een imitatiesignaal en moet worden afgewezen. -
AS-detectie (RFC 8414): met behulp van de uitgever-URL uit de
authorization_servers-array van de PRM, loopt de client langs de bekende kandidaten (/.well-known/oauth-authorization-serveren vervolgens/.well-known/openid-configuration) om de metagegevens van de autorisatieserver op te halen. De uitgever in het geretourneerde document moet byte-identiek zijn met de uitgever waarvoor het is opgehaald - niet “equivalent na normalisatie”, niet “dichtbij genoeg”. Byte-identiek. -
Tokenverwerving (RFC 8707): de client vraagt een token aan bij het tokeneindpunt van de AS, inclusief
resource=<canonical server URI>in zowel de autorisatie- als de tokenaanvragen. Dit bindt het publiek van het token aan deze specifieke MCP-server. De AS geeft een token uit waarvan de doelgroep die bron is, en de client presenteert dit aan de server. -
Geautoriseerde toegang: de client gebruikt het token om de alleen-lezen introspectiemethoden van de MCP-server aan te roepen (
tools/list,resources/list, enz.). Het token bewijst dat de klant geautoriseerd is; de resource-indicator bewijst dat het token voor deze server is geslagen.
Wat RFC 9728 daadwerkelijk biedt
RFC 9728 (Protected Resource Metadata) is een detectiemechanisme. Het antwoordt: “welke autorisatieserver beschermt deze bron, en wat verwacht hij?” Het authenticeert de server niet, valideert het token niet en dwingt geen toegangscontrole af. Dat zijn afzonderlijke zorgen.
Het PRM-document heeft één verplicht veld (resource) en een aantal optionele velden, waarvan authorization_servers het belangrijkst is. De MCP-specificatie verscherpt de optionele mogelijkheden van de RFC: er is minimaal één autorisatieserver vereist. Een PRM-document waarin niets wordt vermeld, wordt als een fout behandeld.
Het veld resource is de canonieke URI van de beschermde bron. De client moet het vergelijken met de server waarmee het contact wilde maken, en een mismatch afwijzen:
// RFC 9728 §3.3: de bronwaarde van de PRM MOET de beschermde
// bron waar de klant zich op richt — eenvoudige vergelijking van tekenreeksen
// de resource-indicator waaraan deze client zijn tokens bindt.
if prm.Resource != c.resource {
return authServerMetadata{}, fmt.Errorf(
"mcp: oauth: protected resource metadata declares resource %q, "+
"expected %q (RFC 9728 §3.3 reject)", prm.Resource, c.resource)
}
Deze controle voorkomt een aanvalsklasse waarbij een kwaadaardig of verkeerd geconfigureerd PRM-document beweert voor een andere bron te spreken. De vergelijking is niet genormaliseerd; het is een directe tekenreeksovereenkomst met de canonieke bron-URI die de client heeft berekend voor de server-URL die deze heeft gekregen.
RFC 8707-bronindicatoren — waarom tokenbinding belangrijk is
Zonder bronindicatoren kan een toegangstoken verkregen van een AS potentieel worden gebruikt op elke bronserver die AS beschermt. Als u twee MCP-servers hebt, bijvoorbeeld een alleen-lezen documentatieserver en een sandbox voor code-uitvoering, beide achter dezelfde identiteitsprovider, kan een token dat voor de ene is verkregen, aan de andere worden gepresenteerd. Dat is een verwarde plaatsvervanger.
RFC 8707 lost dit op door een parameter resource toe te voegen aan de autorisatie- en tokenverzoeken. De AS geeft een token uit waarvan de doelgroep expliciet die bron-URI is. Een goed validerende bronserver wijst een token af waarvan het publiek niet overeenkomt met zijn eigen identiteit.
In de praktijk bevat het tokenverzoek naast de subsidie ook de resource-indicator:
form := url.Values{
"grant_type": {"client_credentials"},
"resource": {c.resource}, // RFC 8707 — bind het token aan de doelgroep
}
Dit komt voor in elk toekenningstype dat de connector gebruikt: clientreferenties, inwisseling van autorisatiecodes en rotatie van vernieuwingstokens. De resource-indicator is niet optioneel; hij is aanwezig in elk tokenverzoek, dus elk token is door de constructie ervan gebonden aan het publiek.
De byte-identieke uitgeverscontrole
De meest veiligheidskritische validatie in de hele stroom is ook de eenvoudigste om te geven en het gemakkelijkst om fout te gaan: de issuerwaarde in het AS-metadatadocument moet byte-identiek zijn aan de issuer die de client heeft gebruikt om de bekende URL te construeren.
Niet hoofdletterongevoelig gelijk. Niet gelijkwaardig na normalisatie van het schema. Niet hetzelfde na het verwijderen van een afsluitende schuine streep. Byte-identiek. RFC 8414 sectie 3.3 is hier expliciet over, en de MCP-specificatie neemt de vereiste over.
// discoverASMetadata: WEIGERT een document waarvan de issuer niet
// BYTE-IDENTIEK is aan de issuer waarvoor het is opgehaald (RFC 8414 §3.3).
// Een afwijkend document is een signaal van identiteitsvervalsing.
if as.Issuer != issuer {
return authServerMetadata{}, fmt.Errorf(
"mcp: oauth: AS metadata at %s declares issuer %q, "+
"expected %q (RFC 8414 §3.3 reject)", cand, as.Issuer, issuer)
}
Waarom zo streng? Omdat een aanvaller die DNS beheert of zich op het netwerkpad bevindt, een metadatadocument kan aanbieden dat verwijst naar zijn eigen tokeneindpunt, terwijl hij beweert een legitieme uitgever te zijn. Als de klant de uitgever normaliseerde voordat hij ging vergelijken, zouden https://auth.example.com en https://AUTH.example.com overeenkomen – en zou het document van de aanvaller worden geaccepteerd. Byte-identieke vergelijking sluit dit af.
Dezelfde discipline is van toepassing op RFC 9207 (validatie van de uitgever van het autorisatieantwoord). Wanneer de client een autorisatiecodestroom start, registreert deze de uitgever op basis van de gevalideerde AS-metagegevens. Bij het terugsturen moet de parameter iss in het antwoord overeenkomen met die geregistreerde waarde (opnieuw byte-voor-byte, geen normalisatie) voordat de autorisatiecode wordt ingewisseld. Dit is de verdediging tegen verwarring: zonder dit zou een kwaadwillende AS de code kunnen onderscheppen en de client deze kunnen laten inwisselen op het token-eindpunt van de aanvaller.
Klantidentificatie: CIMD vervangt DCR
De MCP-specificatie definieert een prioriteitsvolgorde voor de manier waarop een client zichzelf identificeert bij de autorisatieserver:
- Vooraf geregistreerde inloggegevens: de operator verstrekt vooraf een
client_idenclient_secret - CIMD (Client ID Metadata Documents): de client host een JSON-document op een HTTPS-URL; die URL is de
client_id - Dynamische clientregistratie (RFC 7591): de client registreert zichzelf op het registratie-eindpunt van de AS
- Vraag de gebruiker — niet van toepassing op hoofdloze agenten
DCR is verouderd in de release candidate voor de definitieve specificatie gepland voor 2026-07-28, ten gunste van CIMD. De reden is operationeel: DCR creëert een persistente clientstatus op de autorisatieserver. Elke agent die zichzelf registreert, laat een client_id/client_secret-paar achter dat de AS moet opslaan, en niemand volgt of trekt deze in. Voor een vloot agenten is dit een ongecontroleerde wildgroei aan referenties.
CIMD keert het model om. De client host een document op een URL die hij beheert. De AS haalt het document op wanneer het de client moet valideren, slaat het op in de cache volgens HTTP-cacheheaders en slaat niets permanent op. Sleutelrotatie is een documentupdate. Het buiten gebruik stellen van een client betekent het verwijderen van het document. Er verzamelen zich geen weesregistraties op de AS.
De afweging: CIMD vereist dat de client een HTTPS-eindpunt uitvoert. Voor een zelf-hostend bestuursvlak is dat normaal: het vlak voert al HTTPS-services uit. Voor een CLI-tool op een ontwikkelaarslaptop is dit minder het geval. Daarom blijven vooraf geregistreerde inloggegevens de eerste optie in de prioriteitsvolgorde.
Een CIMD-identiteit kan geen gedeeld geheim bevatten (de document-URL is openbaar; een geheim daarin zou een legitimatielek zijn). Clientverificatie maakt gebruik van private_key_jwt (RFC 7523): de client ondertekent een kortstondige JWT met een privésleutel waarvan de openbare tegenhanger wordt gepubliceerd in het veld jwks van het CIMD-document.
De ‘nooit passthrough’-regel
Een structurele verdediging die gemakkelijk over het hoofd wordt gezien: de connector gebruikt alleen een token dat hij zelf voor een specifieke server heeft verkregen via de hierboven beschreven ontdekkingsstroom. Het accepteert nooit een token van een derde partij en stuurt het door naar een MCP-server. Het leest nooit een token van een inkomend verzoek en geeft het door.
Dit is de verdediging van de verwarde plaatsvervanger op protocolniveau. Als een client tokens die hij heeft ontvangen doorstuurt, kan een aanvaller een token presenteren dat is gericht op een bron met lage bevoegdheden en de client deze laten doorsturen naar een bron met hoge bevoegdheden (of omgekeerd: een token met hoge bevoegdheden extraheren door de client te misleiden om deze aan een door de aanvaller bestuurde server te presenteren). Door zijn eigen tokens te verkrijgen en nooit die van iemand anders aan te raken, kan de connector niet als tokenrelais gaan fungeren.
Het doorgeven van tokens wordt niet alleen ontmoedigd; het is structureel onmogelijk. De HTTP-client van de connector voor OAuth-stromen staat los van elke inkomende aanvraaghandler. Er is geen codepad dat een dragertoken van een inkomend verzoek leest en dit naar een uitgaand verzoek schrijft.
SSRF: de verdediging tegen DNS-rebinding
Elke metagegevens en tokeneindpunt-URL die de connector ophaalt, wordt SSRF-beveiligd op twee lagen. De eerste is een pre-flightcontrole: de URL moet HTTPS zijn (behalve loopback voor lokale ontwikkeling) en mag geen letterlijk gereserveerd IP-adres zijn.
De tweede is een beltijdcontrole die de DNS-rebinding TOCTOU sluit. Een hostnaam die tijdens de pre-flight-controle wordt omgezet naar een openbaar IP-adres, kan opnieuw worden gekoppeld aan een privé-IP tegen de tijd dat de socket wordt gebeld. De HTTP-client van de connector installeert een net.Dialer.Control-functie die het concrete opgeloste IP inspecteert tijdens het verbinden en elk gereserveerd adres weigert. Dit is de gezaghebbende controle: de pre-flight is een snelle afwijzing in voor de hand liggende gevallen, maar de beltijdcontrole is degene die er toe doet.
Vergroot de reikwijdte
Een MCP-server kan een verzoek beantwoorden met WWW-Authenticate: Bearer error="insufficient_scope" scope="mcp:tools:list mcp:resources:read". De specificatie (SEP-835/SEP-2350) definieert hoe de client hiermee omgaat: bereken de unie van de scopes die hij eerder heeft opgevraagd en de scopes die de server zojuist heeft uitgedaagd, en verkrijg vervolgens opnieuw een token met die uitgebreide set. De vakbond behoudt eerder verleende machtigingen en voegt de nieuwe toe. De stap-up vindt één keer plaats: een tweede uitdaging met onvoldoende reikwijdte voor hetzelfde verzoek wordt niet opnieuw geprobeerd, om oneindige lussen te voorkomen.
De server mag staatloos zijn in zijn uitdaging: hij noemt alleen de scopes die de huidige bewerking nodig heeft, niet de volledige set die de client mogelijk eerder heeft aangevraagd. Accumulatie aan de clientzijde zorgt ervoor dat dit werkt zonder dat de server de scopegeschiedenis per client bijhoudt.
Wat dit in de praktijk betekent
De OAuth-stroom voor MCP-servers is goed gespecificeerd en richt zich, wanneer strikt geïmplementeerd, op de echte aanvalsoppervlakken: het opnieuw afspelen van tokens op servers, nabootsing van metagegevens, DNS-rebinding SSRF en ongecontroleerde wildgroei van clientreferenties. De byte-identieke issuercontrole, de resource-indicator in elk tokenverzoek en de structurele no-passthrough-regel vormen de belangrijkste verdedigingsmechanismen. Het zijn geen functies die je maar half kunt implementeren; elk ervan is een harde controle die mislukt.
Het moeilijkere probleem is operationeel. Teams die MCP-servers inzetten, moeten:
- Publiceer een geldig PRM-document op
/.well-known/oauth-protected-resourcemet eenresource-veld dat overeenkomt met de canonieke URI van hun server - Gebruik een AS die bronindicatoren ondersteunt – veel identiteitsproviders doen dat nog steeds niet, of beschouwen de parameter
resourceals adviserend in plaats van als doelgroepbindend - Ga van DCR naar CIMD voordat de beëindiging een verwijdering wordt, of registreer de inloggegevens expliciet vooraf
- Inloggegevens vastmaken aan een uitgever om stil hergebruik van meerdere uitgevers te voorkomen wanneer de AS-topologie verandert
De MCP-connectordocumentatie behandelt de operationele configuratie, en het beveiligingsmodel beschrijft hoe het resource-gebonden token in de bredere toegangskaart past.