Skip to main content
Gå til innhold

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å:

  1. Registreres som OAuth-klient i Feides kundeportal
  2. Utfordre uautentiserte forespørsler med 401 + metadata for oppdagelse
  3. Validere Bearer-tokens ved hjelp av RFC 8693 token-utveksling
  4. Hente ut brukerattributter (e-post, navn, osv.) fra validerte JWT-tokens
  5. 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

  1. Logg inn i Feide kundeportal

  2. 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
  3. 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
  4. Koble tjeneste til datakilde

    • Be om tilgang fra tjenesten (opprettet i steg 2) til datakilden (opprettet i steg 3)
    • Godkjenn denne tilgangsforespørselen
  5. 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>
  6. 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

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:

  1. Utveksle et opakt bearer-token for en JWT
  2. Be om spesifikke scopes (e-post, navn, osv.)
  3. Validere JWT-en med signaturverifisering
  4. 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:

  1. Legg til brukerinfo i forespørselskontekst
  2. Videresend til MCP-handler
  3. Utfør forespurt verktøy/ressurs med brukerkontekst

Feilresponser

Kritisk: Returner korrekte HTTP-statuskoder for å unngå omdirigeringsløkker!

ScenarioStatusResponsÅrsak
Ingen Authorization-header401WWW-Authenticate med metadata-URIMå autentisere
Ugyldig Bearer-format401WWW-Authenticate med metadata-URIMå autentisere
Ugyldig/utløpt token401{"error": "invalid_token"}Må re-autentisere
Bruker ikke autorisert for audience403{"error": "insufficient_scope"}Autentisert men forbudt
Audience ikke registrert i Feide503{"error": "service_unavailable"}Serverkonfigurasjonsfeil
Ugyldig klientlegitimasjon503{"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:

  1. Test med minimale scopes: MCP-serverens metadata bør kun annonsere openid i scopes_supported. Dette sikrer at klienten ber om minimale scopes, og Feide vil returnere alle tilgjengelige scopes som standard.

  2. 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.

  3. 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)

  1. Gå til Claude Admin Settings
  2. Naviger til Connectors
  3. Klikk Add custom connector
  4. 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)
  5. Gå til Advanced settings
  6. 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:

  1. Gå til Account Settings i Claude
  2. Finn den konfigurerte MCP-connectoren
  3. Klikk Connect
  4. Fullfør Feide-innloggingsflyten for å autorisere tilgang
  5. 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

Feide-dokumentasjon

Eksempelkode

Biblioteker