MCP-autentisering
Denne guiden beskriver hvordan du implementerer OAuth-autentisering på en MCP-server med Feide som identitetsleverandør. Fokuset er på hva MCP-serveren må implementere for å validere innkommende Bearer-tokens fra AI-klienter.
Oversikt
MCP-serveren fungerer som en OAuth 2.0 Resource Server. Den må:
- Registreres som OAuth-klient i Feides kundeportal
- Utfordre uautentiserte forespørsler med 401 + metadata for oppdagelse
- Validere Bearer-tokens ved hjelp av RFC 8693 token-utveksling
- Hente ut brukerattributter (e-post, navn, osv.) fra validerte JWT-tokens
- Tilby metadata-endepunkter for klientoppdagelse
Forutsetninger: Registrer OAuth-klient i Feide
Før du implementerer autentisering, må du registrere MCP-serveren som OAuth-klient i Feides kundeportal.
Steg-for-steg registrering
-
Logg inn i Feide kundeportal
- Gå til https://kunde.feide.no
- Logg inn med Feide-legitimasjonen din
-
Opprett en tjeneste (OAuth-klient)
- Naviger til «Tjenester»
- Velg «Leveres av [din organisasjon]»
- Klikk «Opprett ny tjeneste»
- Fyll inn tjenestedetaljer:
- Navn: Navnet på MCP-klienten (f.eks. «Claude.ai MCP-integrasjon»)
- Beskrivelse: Kort beskrivelse av tjenesten
- Denne tjenesten representerer MCP-klienten og er ment for organisasjonsomfattende MCP-konfigurasjon (f.eks. for Claude.ai)
- Konfigurer OIDC-innstillinger for denne tjenesten
-
Opprett en datakilde (MCP-serverressurs)
- Naviger til «Datakilder»
- Klikk «Opprett datakilde»
- Konfigurer datakilden - denne representerer tilgang til MCP-serveren din
- Viktig: Noter UUID-en til datakilden - denne må inkluderes i
audience-konfigurasjonsparameteren
-
Koble tjeneste til datakilde
- Be om tilgang fra tjenesten (opprettet i steg 2) til datakilden (opprettet i steg 3)
- Godkjenn denne tilgangsforespørselen
-
Lagre legitimasjon
- Etter opprettelse får du:
- Client ID: OAuth-klientidentifikatoren din
- Client Secret: Hold denne hemmelig, aldri commit til git
- Lagre disse i miljøvariabler:
MCP_CLIENT_ID=din-client-id-her
MCP_CLIENT_SECRET=din-client-secret-her
MCP_AUDIENCE=https://n.feide.no/datasources/<din-datakilde-uuid>
- Etter opprettelse får du:
-
Aktiver støtte for langvarige tokens (kritisk for Claude Desktop)
- Kontakt Feide-support for å aktivere
longterm-scopet på OAuth-klienten din - Dette øker levetiden på access-tokens fra 8 timer til 2 år
- Hvorfor dette er nødvendig: Claude Desktop og andre desktop AI-applikasjoner kan ikke enkelt håndtere token-oppdateringsflyter
- Uten langvarige tokens må brukere koble til MCP-integrasjonen på nytt hver 8. time, noe som gir en uakseptabel brukeropplevelse
- Forespørselsformat: Be Feide-support om å «aktivere longterm-scopet for klient [din-client-id]»
- Etter aktivering vil brukere bli bedt om å samtykke til langtidstilgang under OAuth-flyten
- Sikkerhetsvurdering: Langvarige tokens bør kun brukes for pålitelige desktop-applikasjoner, ikke webapplikasjoner
- Kontakt Feide-support for å aktivere
Viktige sikkerhetshensyn
- Aldri eksponer Client Secret: Lagre i miljøvariabler, ikke i kode
- Bruk HTTPS i produksjon: Token-utveksling skal kun skje over sikre forbindelser
- Roter hemmeligheter regelmessig: Følg organisasjonens sikkerhetspolicyer
- Overvåk mistenkelig aktivitet: Logg alle autentiseringsforsøk
Sikkerhetsmodell: Klient og server i samme tillitssone
I dette oppsettet mottar MCP-serveren et Feide-access-token med alle tillatelsene som er gitt til klienten. Dette betyr at MCP-serveren og klienten må anses å være i samme sikkerhetssone.
Implikasjoner:
- Ikke bruk samme Feide-tjenestekonfigurasjon for MCP-servere fra forskjellige leverandører eller med ulike sikkerhetsnivåer
- Hver distinkte «sikkerhetssone» bør ha sin egen Feide-klientkonfigurasjon
- Hvis du trenger å koble til flere MCP-servere med ulike tillitsnivåer, opprett separate Feide-tjenesteregistreringer for hver
Alternativ modell (ikke støttet av standard MCP-klienter): En alternativ tilnærming ville være at MCP-klienten utfører token-utveksling med Feide og bruker et «målrettet» token med scope spesifikt for hver MCP-server. Dette ville imidlertid kreve egendefinerte MCP-klientimplementasjoner og fungerer ikke ut av boksen med standard MCP-klienter som Claude Desktop eller MCP Inspector.
Steg 1: Håndter uautentiserte forespørsler (401-utfordring)
Når en forespørsel ankommer /mcp uten en Authorization-header, svar med:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://din-server/.well-known/oauth-protected-resource"
Content-Type: application/json
{
"error": "Unauthorized",
"error_description": "Autorisasjon påkrevd. Bruk WWW-Authenticate-header for oppdagelse."
}
Viktige punkter:
WWW-Authenticate-headeren forteller klienten hvor den finner autorisasjonsserverinformasjon- Inkluder
scope-parameter hvis spesifikke scopes kreves
Steg 2: Protected Resource Metadata-endepunkt
Implementer GET /.well-known/oauth-protected-resource (RFC 9728):
{
"resource": "https://din-server.example.com",
"authorization_servers": ["https://auth.dataporten.no"]
}
Dette forteller klienten hvilken autorisasjonsserver (Feide) som skal brukes for å hente tokens.
Steg 3: Authorization Server Metadata (valgfri proxy)
Valgfritt, server GET /.well-known/oauth-authorization-server som proxyer eller returnerer Feides OAuth-konfigurasjon:
{
"issuer": "https://auth.dataporten.no",
"authorization_endpoint": "https://auth.dataporten.no/oauth/authorization",
"token_endpoint": "https://auth.dataporten.no/oauth/token",
"userinfo_endpoint": "https://auth.dataporten.no/openid/userinfo",
"jwks_uri": "https://auth.dataporten.no/openid/jwks",
"scopes_supported": ["openid", "profile", "email", "userid", "groups"],
"code_challenge_methods_supported": ["S256"]
}
Steg 4: Valider Bearer-tokens med token-utveksling
Den anbefalte tilnærmingen for å validere Feide-tokens er å bruke RFC 8693 Token Exchange. Dette lar deg:
- Utveksle et opakt bearer-token for en JWT
- Be om spesifikke scopes (e-post, navn, osv.)
- Validere JWT-en med signaturverifisering
- Hente ut brukerattributter for personalisering
4a. Hent ut Bearer-token
const authHeader = req.headers.authorization;
const match = authHeader.match(/^Bearer\s+(.+)$/i);
const token = match[1]; // Opakt UUID-token fra klient
Hvis header mangler eller er feilformatert, returner 401 med WWW-Authenticate.
4b. Utfør token-utveksling (RFC 8693)
Utveksle det opake tokenet for en JWT med forespurte scopes:
const params = new URLSearchParams({
grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
client_id: process.env.MCP_CLIENT_ID,
client_secret: process.env.MCP_CLIENT_SECRET,
subject_token: token,
subject_token_type: 'urn:ietf:params:oauth:token-type:access_token',
audience: process.env.MCP_AUDIENCE,
scope: 'email name', // Be om nødvendige scopes
});
const response = await fetch('https://auth.dataporten.no/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: params.toString(),
});
const data = await response.json();
const jwt = data.access_token; // JWT-token med claims
Feilhåndtering:
- 400 invalid_target: Audience ikke registrert hos Feide → Returner 503 Service Unavailable
- 400 invalid_client: Feil klientlegitimasjon → Returner 503 Service Unavailable
- 403 insufficient_scope: Bruker har ikke tilgang til audience → Returner 403 Forbidden
- 401 invalid_grant: Token ugyldig/utløpt → Returner 401 Unauthorized
4c. Verifiser JWT-signatur og claims
Bruk et JWT-bibliotek med JWKS-støtte (f.eks. jose for Node.js):
import { jwtVerify, createRemoteJWKSet } from 'jose';
const jwks = createRemoteJWKSet(new URL('https://auth.dataporten.no/openid/jwks'));
const { payload } = await jwtVerify(jwt, jwks, {
issuer: 'https://auth.dataporten.no',
audience: process.env.MCP_AUDIENCE,
});
// payload inneholder nå validerte claims
Hva dette validerer:
- ✅ JWT-signaturen er gyldig (signert av Feide)
- ✅ Token har ikke utløpt (exp-claim)
- ✅ Token ble utstedt av Feide (iss-claim)
- ✅ Token er ment for din server (aud-claim)
4d. Hent ut brukerattributter
JWT-payloaden inneholder brukerinformasjon:
{
"sub": "9f70f418-3a75-4617-8375-883ab6c2b0af", // Bruker-ID
"iss": "https://auth.dataporten.no", // Utsteder
"aud": "https://n.feide.no/datasources/orgdata",// Audience
"exp": 1733673600, // Utløpstidspunkt
"iat": 1733670000, // Utstedelsestidspunkt
"email": "bruker@example.com", // Brukerens e-post (hvis scope er gitt)
"name": "Ola Nordmann", // Brukerens navn (hvis scope er gitt)
"scope": "email name", // Innvilgede scopes
// ... flere claims
}
Bruk av claims:
const userInfo = {
id: payload.sub,
email: payload.email,
name: payload.name,
};
// Personaliser svar
const greeting = `Hei ${userInfo.name} (${userInfo.email})!`;
4e. Håndter manglende claims
Hvis nødvendige claims mangler, håndter det på en god måte:
if (!payload.email) {
// E-post-scope er sannsynligvis ikke konfigurert for klienten i Feide kundeportal
console.warn('E-post-claim mangler - sjekk at klienten har tilgang til email-scope i Feide kundeportal');
// Bruk fallback eller konfigurer riktige scopes i kundeportalen
}
if (!payload.name) {
// Fallback til sub (bruker-ID)
userInfo.name = payload.sub;
}
Steg 5: Cach validerte tokens
For å redusere latens og unngå gjentatte token-utvekslinger, implementer en in-memory cache:
import crypto from 'crypto';
class TokenCache {
constructor(maxSize = 1000) {
this.cache = new Map(); // opakt token-hash -> JWT claims
this.maxSize = maxSize;
}
get(opaqueToken) {
const hash = crypto.createHash('sha256').update(opaqueToken).digest('hex');
const entry = this.cache.get(hash);
if (!entry) return null;
// Sjekk utløp
const now = Math.floor(Date.now() / 1000);
if (now >= entry.claims.exp) {
this.cache.delete(hash);
return null; // Utløpt
}
return entry.claims;
}
set(opaqueToken, claims) {
const hash = crypto.createHash('sha256').update(opaqueToken).digest('hex');
// LRU-fjerning hvis full
if (this.cache.size >= this.maxSize) {
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(hash, { claims, cachedAt: Date.now() });
}
}
Cache-strategi:
- TTL: Bruk JWT-ens
exp-claim (typisk 5-60 minutter) - Nøkkel: SHA256-hash av opakt token (for personvern)
- Verdi: Validerte JWT-claims
- Fjerning: LRU når cachen er full + automatisk utløpssjekk
- Opprydding: Periodisk opprydding hvert 5. minutt for å fjerne utløpte oppføringer
Steg 6: Håndter autentiserte forespørsler
Etter vellykket validering:
- Legg til brukerinfo i forespørselskontekst
- Videresend til MCP-handler
- Utfør forespurt verktøy/ressurs med brukerkontekst
Feilresponser
Kritisk: Returner korrekte HTTP-statuskoder for å unngå omdirigeringsløkker!
| Scenario | Status | Respons | Årsak |
|---|---|---|---|
| Ingen Authorization-header | 401 | WWW-Authenticate med metadata-URI | Må autentisere |
| Ugyldig Bearer-format | 401 | WWW-Authenticate med metadata-URI | Må autentisere |
| Ugyldig/utløpt token | 401 | {"error": "invalid_token"} | Må re-autentisere |
| Bruker ikke autorisert for audience | 403 | {"error": "insufficient_scope"} | Autentisert men forbudt |
| Audience ikke registrert i Feide | 503 | {"error": "service_unavailable"} | Serverkonfigurasjonsfeil |
| Ugyldig klientlegitimasjon | 503 | {"error": "service_unavailable"} | Serverkonfigurasjonsfeil |
Hvorfor ulike statuskoder er viktige:
- 401 Unauthorized: Klient bør re-autentisere → Kan forårsake omdirigeringsløkke hvis server er feilkonfigurert
- 403 Forbidden: Klient er autentisert men mangler tillatelse → Ingen retry
- 503 Service Unavailable: Serverkonfigurasjonsfeil → Klient vet at den ikke skal prøve autentisering på nytt
Komplett forespørselsflyt
Klient MCP-server Feide
│ │ │
│─POST /mcp (ingen token)─────────>│ │
│<─────────────────401 + WWW-Auth──│ │
│ │ │
│─GET /.well-known/oauth-protected-resource─>│ │
│<───────────────{authorization_servers}─────│ │
│ │ │
│───────────────OAuth-flyt med PKCE──────────────────────────>│
│<────────────────opakt access_token──────────────────────────│
│ │ │
│─POST /mcp + Bearer token────────>│ │
│ │ │
│ │─Token Exchange (RFC 8693)─>│
│ │ (opakt → JWT) │
│ │ + forespør scopes │
│ │<────────JWT med claims─────│
│ │ │
│ │─Verifiser JWT-signatur────>│
│ │ via JWKS │
│ │<────────JWKS-respons───────│
│ │ │
│<───────────MCP-respons──────────│ │
│ (personalisert med │ │
│ navn, e-post fra JWT) │ │
Testing med MCP Inspector
Bruk MCP Inspector for å teste OAuth-konfigurasjonen og verifisere at autentiseringen fungerer korrekt.
Kjør Inspector
npx @modelcontextprotocol/inspector
Inspector tilbyr et web-grensesnitt for testing av MCP-servere, inkludert OAuth-autentiseringsflyter.
Verifiser OIDC-konfigurasjon
Etter oppsett av OAuth-klient og datakilde, bruk Inspector for å verifisere konfigurasjonen:
-
Test med minimale scopes: MCP-serverens metadata bør kun annonsere
openidiscopes_supported. Dette sikrer at klienten ber om minimale scopes, og Feide vil returnere alle tilgjengelige scopes som standard. -
Sjekk access-tokenets scopes: Etter autentisering i Inspector, undersøk scopene som er inkludert i access-token-responsen. Du bør se etter:
- Scopes som gir tilgang til bruker-ID og navn (f.eks.
userid,name) - Et scope som representerer tilgang til MCP-serveren din i formatet:
gk_<uuid>_<basic-scope-name>
gk_-prefikset indikerer et «gatekeeper»-scope som gir tilgang til en spesifikk datakilde. UUID-delen bør samsvare med datakildenes UUID. - Scopes som gir tilgang til bruker-ID og navn (f.eks.
-
Feilsøk manglende scopes: Hvis forventede scopes mangler:
- Verifiser at datakilden har riktige scopes konfigurert i Feide
- Verifiser at tjenesten har bedt om tilgang til disse scopene
- Sjekk at tilgangsforespørselen er godkjent
Konfigurere MCP-server i Claude
Når MCP-serveren kjører og OAuth er konfigurert i Feide, kan du koble den til Claude.
Administratoroppsett (organisasjonsomfattende)
- Gå til Claude Admin Settings
- Naviger til Connectors
- Klikk Add custom connector
- Fyll inn connector-detaljer:
- Name: Et beskrivende navn for MCP-serveren
- MCP URL: URL-en der MCP-serveren kjører (f.eks.
https://din-server.example.com/sse)
- Gå til Advanced settings
- Skriv inn Client ID og Client Secret fra Feide-tjenestekonfigurasjonen
Klientkonfigurasjonen er kun synlig for administratorer, ikke for individuelle brukere.
Brukeraktivering
Etter at administrator har konfigurert connectoren, kan hver bruker aktivere den med sine personlige legitimasjoner:
- Gå til Account Settings i Claude
- Finn den konfigurerte MCP-connectoren
- Klikk Connect
- Fullfør Feide-innloggingsflyten for å autorisere tilgang
- MCP-serveren er nå aktiv med brukerens personlige tillatelser
Feilsøking
Vanlige problemer
«invalid_target»-feil
- Audience-en din er ikke registrert hos Feide
- Kontakt Feide-support for å registrere audience-identifikatoren
- Verifiser at audience-strengen samsvarer nøyaktig med det Feide forventer
«insufficient_scope»-feil
- Brukerens token har ikke tilgang til forespurt audience
- Bruker må autentisere med riktige scopes
- Verifiser at bruker har tillatelse til å få tilgang til tjenesten din
«invalid_client»-feil
- Client ID eller client secret er feil
- Verifiser legitimasjonen i Feide kundeportal
- Sjekk at miljøvariabler er satt korrekt
JWT-signaturverifisering feiler
- JWKS-endepunktet er utilgjengelig
- Token ble ikke utstedt av Feide
- Systemklokken er ute av synk (sjekk NTP)
Token alltid utløpt fra cache
- Sjekk at systemtiden er korrekt
- JWT exp-claim kan være for kort
- Cache-TTL-beregning kan være feil
Referanser
Standarder og spesifikasjoner
- MCP Authorization Specification
- RFC 8693 - OAuth 2.0 Token Exchange
- RFC 9728 - OAuth Protected Resource Metadata
- RFC 6750 - OAuth 2.0 Bearer Token Usage
- RFC 8707 - Resource Indicators
Feide-dokumentasjon
- Feide kundeportal - Registrer OAuth-klienter
- Feide-dokumentasjon - Generell Feide-dokumentasjon
- Token Exchange Guide - Detaljer om token-utveksling
Eksempelkode
- demo-mcp-feide-nodejs - Komplett eksempelimplementasjon av MCP-server med Feide-autentisering
Biblioteker
- jose - JWT-validering med JWKS-støtte for Node.js
- @modelcontextprotocol/sdk - MCP SDK for Node.js