Hur en MCP-server anropar ditt API å användares vägnar: en produktionsstrategi för tokens
Förklarar outbound-tokenstrategin för MCP-servrar: varför token pass-through och M2M misslyckas, och hur tokenutbyte håller behörigheter i linje med användarna.
YijunDeveloper
Sluta slösa veckor på användarautentisering
Lansera säkra appar snabbare med Logto. Integrera användarautentisering på några minuter och fokusera på din kärnprodukt.
Hur en MCP-server anropar ditt API å användares vägnar: en produktionsstrategi för tokens#
I vår tidigare artikel delade vi med oss av vår övergripande erfarenhet av att bygga Logto remote MCP-server. Den här artikeln går på djupet kring arkitekturell design och OAuth-flödet.
Med MCP-servern som gräns finns det två ben i autentiseringen för en remote MCP-server: inbound och outbound.
Inbound: MCP-klienten (VS Code, Cursor, etc.) loggar in genom OAuth, hämtar en åtkomsttoken och använder den för att få åtkomst till din MCP-server
Outbound: när verktygsanrop hanteras begär MCP-servern ditt eget affärs-API å användarens vägnar
Inbound-benet är väl definierat i MCP-specen, med många diskussioner och implementationer i communityn. Vi skrev även en implementeringsguide tidigare. Outbound-benet har fått mycket mindre uppmärksamhet: MCP-servern håller bara en token för åtkomst till sig själv. Hur kan den anropa ditt affärs-API i användarens namn?
Detta var frågan vi hela tiden brottades med när vi byggde Logto MCP-servern. Logto Cloud är en typisk B2B multi-tenant produkt: en användare kan tillhöra flera tenants och behörigheterna kommer från rollen användaren har i varje tenant. AI:n måste inte bara agera som användaren, den måste också hamna i rätt tenant.
Den här artikeln återger vår faktiska beslutsprocess:
Varför MCP-servern ska distribueras separat, som en egen skyddad resurs
Två outbound-upplägg som vi avfärdade (token pass-through och M2M-token) och problemen med dem
Den slutliga designen, tokenutbyte plus subject token, inklusive hur organisationstokens hanterar multi-tenancy
Några principer att hålla fast vid när du bygger något liknande
Ur OAuth-perspektiv spelar en remote MCP-server två roller samtidigt:
För MCP-klienten är den en resursserver: klienten måste ta med sig en token för att nå den
För affärs-API:t är den en klient: den tar med sig en token för att nå någon annan
Med andra ord spelar MCP-servern olika roller på respektive sida, och varje sida använder en olika token. Token som MCP-klienten hämtar via OAuth har MCP-servern som audience, så affärs-API:t kommer att neka den när den validerar audience.
Utgående ben är alltså i grunden ett delegeringsproblem: hur låter man MCP-servern anropa downstream-API:er som användaren, inom användarens rättigheter, utan att hålla användarens inloggningsuppgifter?
Arkitekturbeslut: MCP-server som fristående tjänst#
Att bygga in MCP-endpoint i affärs-API-tjänsten, med samma process och samma auth-stack, ser ut som det mest direkta alternativet. Efter övervägande valde vi en fristående distribution:
Riskisolering: När vi tog beslutet var det officiella MCP-SDK:t inte redo för produktion och protokollet förändrades snabbt (t.ex. bytet från SSE till Streamable HTTP). Logto är en IAM-tjänst och tillgängligheten för inloggning är kritisk. Med fristående distribution, om MCP-servern går ner så påverkas bara AI-ingången. Huvudtjänsten påverkas inte.
Oberoende utvecklingstakt: MCP-ekosystemet förändras varje vecka och klientkompatibilitetsproblem kräver ibland snabba hotfixar, medan kärntjänsten har ett strikt releaseflöde med regressionstester. Separata distributioner hindrar att de två hämmar varandras hastighet.
Fria val av infrastruktur: en fristående tjänst kan välja den runtime som passar bäst. Logto MCP-server kör på Cloudflare Workers: stateless, skalar per anrop och nästan noll drift. Det inbäddade alternativet ger inte denna valfrihet.
MCP-servern är distribuerad på egen domän, mcp.logto.io, utan privat koppling till huvudtjänsten. Om vi vill open-sourca den i framtiden, är inget i vägen.
Det inbäddade alternativet slipper heller inte tokenproblematiken: MCP-endpoint och affärs-API:et skulle dela samma resursidentifierare, så token MCP-klienten hämtar får fulla API-behörigheter. Frågan om "vems behörighet detta anrop körs med" flyttas således från cross-service tokenutbyte till lokal behörighetshantering, men själva problemet kvarstår. Fristående distribution tvingar oss att tydligt rita ut behörighetsgränsen, vilket resten av artikeln handlar om.
Outbound-diskussionen börjar vid en grundläggande fråga: vad ska token MCP-klienten får ha som audience?
Att återanvända affärs-API:ts resursidentifierare är det mest direkta: Logtos Management API är redan en standard OAuth-skyddad resurs, så MCP-klienten skulle kunna begära en token direkt och MCP-servern bara vidarebefordrar den oförändrad. Många tidiga MCP-serverimplementationer gjorde exakt detta.
Priset: om MCP-token har affärs-API:t som audience så försvinner hela behörighetsgränsen.
Dina noggrant utformade MCP-verktyg blir kosmetika: själva token kan anropa hela API:t utan att gå via dina verktyg
Skadeomfånget vid en läckt token ökar från "några kontrollerade operationer via MCP-servern" till "hela Management API:t"
Självständig revision, rate limiting och behörighetsspecifik åtkomst för MCP-scenarion har inget stöd
Alltså: MCP-servern är en separat skyddad resurs, med egen resursidentifierare (https://mcp.logto.io) och egna scope.
Detta gör behörighetsgränsen tydlig, och konkretiserar outboundfrågan: token kan bara nå MCP-servern, så vad ska MCP-servern använda för att nå API:t?
Det första sambandet kom från en naturlig analogi.
Logto Console är en SPA. För att anropa Management API loggar användaren in via OAuth i webbläsaren, hämtar en token med Management API som audience och frontenden anropar API:t direkt.
Kan då inte MCP-servern fungera som en sorts Console? Låta MCP-klienten begära affärs-API:ts token vid inloggningen, och MCP-servern vidarebefordrar den rakt igenom utan förändring:
Fördelen här är att det är extremt enkelt: MCP-servern bara vidarebefordrar tokens. Men det har uppenbara problem:
För det första strider det direkt mot beslutet om behörighetsgränsen ovan. Token pass-through kräver att MCP-klienten innehåller affärs-API:ts token, vilket ju är exakt det vi nyss valt bort.
För det andra är MCP-servern inte längre en verklig skyddad resurs. Token den tar emot har inte den egna audience, så audience-valideringen blir värdelös och handlar bara om "verifiera signatur och issuer". Detta stämmer inte heller med hur MCP-specen definierar auktorisation (MCP-servern bör agera som resursserver och deklarera sig själv via RFC 9728 Protected Resource Metadata). Det utger sig i grunden för att vara en backend som en SPA i browsern.
För det tredje, MCP-klienter samarbetar ej. En spec-kompatibel MCP-klient begär tokens utifrån Protected Resource Metadata och audience kommer vara MCP-servern. Det finns inget standardiserat sätt att få den att istället begära affärs-API:ts token - så detta funkar ej på klientsidan.
Om användarens token inte funkar, vad sägs om MCP-serverns egen identitet?
Tilldela MCP-servern en M2M (maskin-till-maskin) applikation, hämta en token med client credentials och använd den för att anropa affärs-API:t. Detta är också standardpraxis mellan interna tjänster.
Ingående autentisering fungerar nu: MCP-klientens token har MCP-servern som audience; MCP-servern validerar den, och utför jobbet med sin egen M2M-token.
Problemet: M2M-token har rättigheter som är helt frånkopplade från användarens behörigheter:
Behörighetsöverskridning: M2M-token står för "vad MCP-servern kan göra", inte "vad denna användare kan göra". En användare med läsbehörighet kan radera applikationer via MCP, eftersom M2M-token har full behörighet
Identitetsförlust: downstream-API:t ser alltid M2M-applikationen som anropskälla och loggar kan ej spåras till en faktisk person
Förvirrad tjänare: MCP-servern blir en proxy med hög behörighet, och vem som helst som får den att göra ett anrop får därmed använda dess rättigheter
M2M passar scenarion utan användarkontext, t.ex. schemalagda jobb och system-till-system-sync. En MCP-server är annorlunda: varje anrop initieras av en specifik användare, så det ska exekveras med den användarens identitet och rättigheter.
Den slutliga designen: tokenutbyte + subject token#
Sätter man ihop lärdomarna och kraven blir det tydligt:
Token MCP-klienten har får bara nå MCP-servern (från lärdom i alternativ 1)
Vid downstream-anrop måste MCP-servern agera som användaren, med användarens behörighet (från lärdom i alternativ 2)
Detta pekar mot en etablerad mekanism: tokenutbyte (RFC 8693). MCP-servern tar en credential som representerar användaren, och byter den mot en downstream-API-token vid auth-servern. Användarens identitet och behörighet sparas genom utbytet.
I Logto är denna "credential som representerar användaren" från user impersonation-funktionen: subject token. Det är ett kortlivat credential som servern hämtar för en specifik användare, vilket betyder att "nästa tokenutbyte sker som denna användare". Användaren behöver inte skapa eller konfigurera något - hela flödet är automatiskt. Subject token är kortlivad, engångs och förfaller direkt efter användning.
Det är fyra roller i arkitekturen. MCP-servern är fristående på mcp.logto.io:
Hela tokenflödet bakom ett verktygsanrop:
Steg för steg:
① Ingående validering. MCP-klienten anropar ett verktyg med användartoken. Token har MCP-serverns resursidentifierare i audience och scope är mcp:all. MCP-servern verifierar signatur, issuer, audience och scope och hämtar ut användarens identitet. Ingående slutar här. Denna token går aldrig downstream.
② Serviceidentitet. MCP-servern hämtar sin egen M2M-credential och får en access token med separat scope, access:mcp:api. Detta scope har bara ett syfte: att anropa endpointen i nästa steg.
③ Begära subject token, hela kedjans nyckelsteg. MCP-servern anropar POST /api/mcp/subject-tokens, en endpoint som Cloud öppnat särskilt för MCP, och visar två credentials samtidigt:
Authorization-headern: M2M-token, bevisar "jag är den officiella MCP-servern"
x-mcp-user-token-header: användarens token, bevisar "den här användaren har bemyndigat mig, och bemyndigandet är fortfarande giltigt"
Cloud verifierar användartoken fullständigt: signatur, issuer, utgångsdatum, audience måste vara MCP-serverns resursidentifierare, och scope måste innehålla mcp:all. Efter verifiering kommer userId direkt från tokenns sub-claim. Endpointen har ingen parameter för att själv välja användare.
Den här utformningen gör att M2M-credential inte kan missbrukas. Om endpointen accepterade ett godtyckligt userId skulle vem som helst med M2M-credential kunna utge sig för vilken användare som helst. Med detta upplägg kan MCP-servern bara byta credential för en användare om ett giltigt användarbehörighet samtidigt presenteras.
Den utgivna subject token är kortlivad och kan bara användas en gång. Den cachas aldrig och begärs alltid färsk för varje anrop.
④ Tokenutbyte mot arbetstoken. Med subject token görs ett standardutbyte för att få två typer av tokens efter behov:
Cloud API-token: för användarnivå-operationer i användarens namn, t.ex. lista eller skapa tenants
Org-token: scope till måltenant (varje tenant i Logto Cloud mappas mot en organisation). Token ges ut i användarens namn och får exakt användarens rollbehörighet i tenant
⑤ Outbound-anrop. Affärs-API anropas med utbytt token, resultatet skickas tillbaka till MCP-klienten.
Multitenancy-konteksten löses också i utbytessteget: tenant hanteras i exchange-lagret, inte i kopplingslagret. list_tenants listar valmöjligheter med Cloud API-token, användaren väljer en i konversationen och MCP-servern byter då till rätt org-token. Ett endpoint täcker alla tenants, ingen per-tenant-distribution behövs, och ny tenant finns tillgänglig direkt om den skapats mitt i konversationen.
Jämför med misstagen från de tidigare alternativen - varje fall täcks:
Användartokens skadeområde är begränsat (från alternativ 1): audience är bara MCP-servern. Om någon får tag på den går det bara att anropa kontrollerade verktyg
M2M-token kan inte missbrukas (från alternativ 2): den anropar ej affärs-API:t direkt och bevisar endast serviceidentitet. Att få subject token kräver samtidigt en giltig användartoken; alltså kan MCP-servern bara göra utbyte för godkända användare. Confused deputy-problemet är löst
Ingen användarcredential lagrad: subject tokens begärs vid behov och kasseras direkt. MCP-servern lagrar bara sin egen M2M-credential
Revokering funkar direkt: när användaren drar tillbaka bemyndigandet blir user token ogiltig, x-mcp-user-token misslyckas och outbound-kedjan stannar
Behörigheter följer användaren: utbytta token ges ut som användaren. En read-only användare förblir läsbehörig via MCP och försök till privilegie-escalering stoppas av auth-servern. Loggning visar riktig användaridentitet
Retrospektivt har M2M-credentialn nu rätt arbetsuppgift: den bevisar "vem jag är", medan förmågan att "agera som användaren" måste bytas på plats med giltig användarbehörighet.
Hela kedjan visar att tokenstrategin för en remote MCP-server sammanfattas i några punkter:
Inbound och outbound är två separata autentiseringsben, och MCP-token ska ha MCP-servern själv som audience
Outbound löses med tokenutbyte: om din auth-server stöder utbyte direkt från inbound-token räcker standardutbyte; om inte (t.ex. Logtos token exchange kräver subject token) använd ett impersonation-upplägg för att starta utbytet serverside
Multi-tenancy ändrar inte mekanismen. Organisationskontext är då en parameter vid utbyte, och single-tenant produkter hoppar bara över det
AI:n agerar för användaren, så downstream-tokenbehörigheter måste spegla den användaren exakt
Ett enkelt test: låt säga att MCP-klientens token läcker ut. Det enda en angripare ska kunna göra är att anropa de kontrollerade verktygen på MCP-servern, fortfarande inom användarens rättighetsområde. Om de kan nå hela API:t direkt är behörighetsgränsen trasig.
MCP-ekosystemet utvecklas fortfarande snabbt. Inbound-auth är väl täckt av specen, medan "hur MCP-servern anropar downstream" fortfarande är upp till varje team. Vi hoppas vår praktik blivit en användbar referens.