De evolutie naar OIDC in de Italiaanse overheidsidentiteit

Al meer dan tien jaar is de Italiaanse digitale identiteit gebaseerd op het protocol SAML 2.0: robuust, goed gestandaardiseerd, maar ontworpen in een pre-mobiel tijdperk. Met de explosie van native applicaties voor smartphones en de verspreiding van Single Page Applications, SAML heeft zijn beperkingen getoond: uitwisseling van omvangrijke XML, slechte mobiele ondersteuning, complexe integratie in SPA's.

De overgang naar OpenID Connect (OIDC) het was onvermijdelijk. In januari 2023 publiceerde AgID de OpenID Connect-richtlijnen in SPID en de OpenID Connect-richtlijnen in CIE, waardoor de standaard gebaseerd op OAuth 2.0 officieel wordt als een pad van evolutie van de identiteit van de Italiaanse overheid. De PNRR-doelstelling vereist dat tegen maart 2026 alle 16.500 Italiaanse PA's elektronische identificatie via SPID of CIE adopteren.

Waarom OpenID Connect versus SAML voor PA

  • Lichtgewicht tokens: JWT in plaats van XML SAML-bewering - payload verminderd met 60-70%
  • Mobiel-eerst: OAuth 2.0 met PKCE ontworpen voor native apps en SPA's
  • Modern ecosysteem: bibliotheken beschikbaar voor elke taal/framework
  • Gestandaardiseerde federatie: OpenID Federation 1.0 voor automatische vertrouwensketen
  • Op weg naar EUDI-portemonnee: OpenID4VP en OpenID4VCI gebruiken dezelfde OIDC-primitieven

Architectuur van de SPID/CIE OIDC Federatie

De OIDC-federatie voor SPID en CIE is gebaseerd op OpenID Federatie 1.0, een standaard die de kettingconstructie automatiseert vertrouwen tussen entiteiten. Dit overtreft het handmatige model van SAML-metadataregistratie, waarbij elke SP handmatig metadata moest uitwisselen XML bij elke IdP.

Entiteiten in de Federatie

  • Vertrouwensanker (TA): AgID is het vertrouwensanker van de Italiaanse federatie. Publiceert de entiteitsconfiguratie naar het bekende eindpunt.
  • Tussenliggend: door AgID geautoriseerde aggregators die meerdere RP's verenigen (bijv. regionale PA's, gemeentelijke aggregators).
  • Vertrouwende Partij (RP): de applicatie die gebruikers authenticeert via SPID/CIE OIDC.
  • Identiteitsprovider (OP): SPID-providers (Poste Italiane, Aruba, InfoCert, etc.) of de CIE IdP van het Ministerie van Binnenlandse Zaken.

Entiteitsconfiguratie en vertrouwensketen

// Entity Configuration di un Relying Party
// Disponibile al well-known endpoint:
// GET https://my-service.comune.it/.well-known/openid-federation

{
  "iss": "https://my-service.comune.it",
  "sub": "https://my-service.comune.it",
  "iat": 1710000000,
  "exp": 1741536000,
  "jwks": {
    "keys": [
      {
        "kty": "EC",
        "kid": "rp-sig-key-2025",
        "crv": "P-256",
        "x": "...",
        "y": "...",
        "use": "sig"
      }
    ]
  },
  "metadata": {
    "openid_relying_party": {
      "application_type": "web",
      "client_id": "https://my-service.comune.it",
      "client_registration_types": ["automatic"],
      "redirect_uris": [
        "https://my-service.comune.it/callback"
      ],
      "response_types": ["code"],
      "grant_types": ["authorization_code"],
      "scope": "openid profile email",
      "token_endpoint_auth_method": "private_key_jwt",
      "id_token_signed_response_alg": "ES256",
      "subject_type": "pairwise",
      "contacts": ["tech-team@comune.it"]
    }
  },
  "authority_hints": [
    "https://registry.spid.gov.it"
  ]
}

Implementeer de autorisatiecodestroom met PKCE

De aanbevolen stroom voor SPID/CIE OIDC isAutorisatiecodestroom met PKCE (Proofsleutel voor code-uitwisseling, RFC 7636). PKCE beschermt tegen onderschepte autorisatiecodes en is verplicht voor mobiele apps en aanbevolen voor webapps.

Stap 1: PKCE-generatie en autorisatieverzoek

// TypeScript - Authorization Request con PKCE

import crypto from "crypto";

function generatePKCE() {
  // code_verifier: stringa casuale 43-128 caratteri
  const codeVerifier = crypto.randomBytes(32)
    .toString("base64url");

  // code_challenge: SHA256 del verifier, base64url encoded
  const codeChallenge = crypto.createHash("sha256")
    .update(codeVerifier)
    .digest("base64url");

  return { codeVerifier, codeChallenge };
}

async function initiateLogin(req: Request, res: Response) {
  const { codeVerifier, codeChallenge } = generatePKCE();
  const state = crypto.randomBytes(16).toString("base64url");
  const nonce = crypto.randomBytes(16).toString("base64url");

  // Salva in sessione (server-side)
  req.session.pkce = { codeVerifier, state, nonce };

  // Costruisci l'Authorization Request
  const params = new URLSearchParams({
    response_type: "code",
    client_id: "https://my-service.comune.it",
    redirect_uri: "https://my-service.comune.it/callback",
    scope: "openid profile",
    // Per SPID LoA 2 (identità verificata con password forte)
    acr_values: "https://www.spid.gov.it/SpidL2",
    state,
    nonce,
    code_challenge: codeChallenge,
    code_challenge_method: "S256",
    // Parametri SPID specifici
    prompt: "login",
  });

  // Scopri il provider selezionato tramite OP Selector o discovery
  const opAuthEndpoint = await discoverOPEndpoint("https://idp.poste.it");

  res.redirect(`${opAuthEndpoint}?${params.toString()}`);
}

Stap 2: Terugbellen en tokenuitwisseling

// TypeScript - Gestione callback e token exchange

async function handleCallback(req: Request, res: Response) {
  const { code, state, error } = req.query;
  const session = req.session.pkce;

  // Verifica state per prevenire CSRF
  if (state !== session.state) {
    return res.status(400).json({ error: "State mismatch" });
  }

  if (error) {
    return res.status(400).json({ error });
  }

  // Token Request - usa private_key_jwt per autenticarsi al token endpoint
  const clientAssertion = await createClientAssertion({
    iss: "https://my-service.comune.it",
    sub: "https://my-service.comune.it",
    aud: "https://idp.poste.it/oauth/token",
    jti: crypto.randomBytes(16).toString("hex"),
    iat: Math.floor(Date.now() / 1000),
    exp: Math.floor(Date.now() / 1000) + 60,
  });

  const tokenResponse = await fetch("https://idp.poste.it/oauth/token", {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      grant_type: "authorization_code",
      code: code as string,
      redirect_uri: "https://my-service.comune.it/callback",
      client_id: "https://my-service.comune.it",
      client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
      client_assertion: clientAssertion,
      code_verifier: session.codeVerifier,
    }),
  });

  const tokens = await tokenResponse.json();

  // Verifica e decodifica l'ID Token
  const idToken = await verifyIdToken(tokens.id_token, {
    expectedNonce: session.nonce,
    expectedAudience: "https://my-service.comune.it",
  });

  // idToken.sub = identificatore opaco pairwise dell'utente
  // idToken.acr = livello di garanzia (LoA) ottenuto
  console.log("User authenticated:", idToken.sub);
  console.log("LoA achieved:", idToken.acr);
}

Niveaus van zekerheid (LoA) in SPID en CIE

Een van de belangrijkste aspecten van de identiteit van de overheid is de Niveau van zekerheid (LoA), die aangeeft hoe betrouwbaar het authenticatieproces is. SPID definieert drie niveaus:

Niveau ACR-URI Methode Typisch gebruik
SpidL1 https://www.spid.gov.it/SpidL1 Gebruikersnaam + wachtwoord Diensten met laag risico, niet-gevoelige gegevensraadpleging
SpidL2 https://www.spid.gov.it/SpidL2 Gebruikersnaam + wachtwoord + OTP/push Standaard PA-diensten (declaraties, aanvragen, betalingen)
SpidL3 https://www.spid.gov.it/SpidL3 Gecertificeerd fysiek apparaat Operaties met een hoog risico, gekwalificeerde elektronische handtekening

Voor CIE (elektronische identiteitskaart), is het betrouwbaarheidsniveau altijd LoA3 (het maximum). gebruikte de CIE NFC-chip, omdat hiervoor fysiek bezit van het document en de pincode vereist is. Dit maakt CIE de voor de hand liggende keuze voor services die sterke authenticatie vereisen.

UserInfo-eindpunt- en SPID/CIE-kenmerken

Na het inloggen kan de Relying Party de kenmerken van de gebruiker ophalen van het UserInfo Endpoint met behulp van het Access Token. De attributen die beschikbaar zijn in SPID OIDC worden gedefinieerd in het technische profiel:

// GET /userinfo
// Authorization: Bearer <access_token>

// Risposta UserInfo SPID
{
  "sub": "c5d9b2f3-1a4e-4b8d-9f2a-3e5c7d8f9b1a",  // pairwise pseudonimo
  "https://attributes.spid.gov.it/name": "Mario",
  "https://attributes.spid.gov.it/familyName": "Rossi",
  "https://attributes.spid.gov.it/fiscalNumber": "RSSMRA90A15H501Z",
  "https://attributes.spid.gov.it/dateOfBirth": "1990-01-15",
  "https://attributes.spid.gov.it/placeOfBirth": "Roma",
  "https://attributes.spid.gov.it/countyOfBirth": "RM",
  "https://attributes.spid.gov.it/idCard": "MRTMRA90A15H501Z",
  "https://attributes.spid.gov.it/address": {
    "formatted": "Via Roma 1, 00100 Roma RM"
  },
  "https://attributes.spid.gov.it/email": "mario.rossi@example.com",
  "https://attributes.spid.gov.it/mobilePhone": "+39 333 1234567",
  "acr": "https://www.spid.gov.it/SpidL2"
}

// ATTENZIONE: richiedere solo gli attributi strettamente necessari
// nell'Authorization Request tramite il parametro 'claims':
// "claims": {
//   "userinfo": {
//     "https://attributes.spid.gov.it/fiscalNumber": {"essential": true},
//     "https://attributes.spid.gov.it/name": null
//   }
// }

Privacy en minimalisatie van SPID-kenmerken

Het opvragen van de belastingcode van de gebruiker zonder dat dit echt nodig is, is een schending van het GDPR-minimalisatieprincipe. De AgID-richtlijnen specificeren dat elk aangevraagd attribuut moet worden gemotiveerd door de specifieke functionaliteit van de dienst, e aangegeven in het privacybeleid. Tijdens de AgID-audit kan het niet verantwoorden van de gevraagde kenmerken voorkomen leiden tot intrekking van de accreditatie.

SDK en officiële bibliotheken voor SPID/CIE OIDC

Het project Ontwikkelaars Italië (developers.italia.it) biedt officiële SDK's in verschillende talen om SPID/CIE OIDC-integratie te vereenvoudigen:

## SDK Ufficiali Developers Italia - SPID/CIE OIDC Federation

# Python (Django)
pip install spid-cie-oidc

# Configurazione in Django settings.py
INSTALLED_APPS = [
    ...
    "spid_cie_oidc.entity",
    "spid_cie_oidc.relying_party",
]

OIDCFED_DEFAULT_TRUST_ANCHOR = "https://registry.spid.gov.it"

OIDCFED_IDENTITY_PROVIDERS = {
    "spid": "https://registry.spid.gov.it",
    "cie": "https://idserver.servizicie.interno.gov.it"
}

# Configura automaticamente gli endpoint di federazione
# e gestisce la trust chain dinamicamente

# Java
# Maven: eu.italia.gov:spid-cie-oidc-java:latest

# .NET
# NuGet: AGID.SPID.OIDC

# Node.js (Express)
npm install spid-cie-oidc-node

# PHP
composer require italia/spid-php-lib

JARM: JWT-gebaseerde autorisatieresponsmodus

De SPID/CIE OIDC-richtlijnen bevelen het gebruik aan van JARM (JWT-autorisatieresponsmodus) beschermen het autorisatieantwoord. In plaats van voorbij te gaan code e state in duidelijke tekst als queryparameters, JARM vat het antwoord samen in een JWT ondertekend door de identiteitsprovider.

// Authorization Request con JARM
{
  "response_type": "code",
  "response_mode": "form_post.jwt",  // JARM mode
  "client_id": "https://my-service.comune.it",
  ...
}

// Risposta JARM dal IdP (form POST al redirect_uri)
// Il parametro 'response' contiene un JWT firmato:
// Header:
{ "alg": "ES256", "kid": "idp-key-2025" }
// Payload:
{
  "iss": "https://idp.poste.it",
  "aud": "https://my-service.comune.it",
  "code": "SplxlOBeZQQYbYS6WxSbIA",
  "state": "af0ifjsldkj",
  "iat": 1710000000,
  "exp": 1710000180
}

// Vantaggi JARM:
// 1. Autenticità: garantisce che la risposta venga dall'IdP corretto
// 2. Integrità: impossibile modificare code/state in transito
// 3. Non-repudiation: log auditabili con prova crittografica

Sessiebeheer en tokenvernieuwing

Een cruciaal aspect van de identiteit van de overheid is het correcte beheer van sessies. In tegenstelling tot de particuliere sector, PA-diensten moeten strenge beperkingen respecteren op het gebied van sessieduur en uitloggen.

// Gestione sessione sicura per servizi PA

interface SessionConfig {
  maxAge: number;           // durata massima sessione in secondi
  inactivityTimeout: number; // timeout per inattività
  requireReauth: boolean;    // richiede riautenticazione per operazioni critiche
}

// Configurazione raccomandata per servizi PA
const SESSION_CONFIG: SessionConfig = {
  maxAge: 3600,          // 1 ora massimo (SPID raccomanda max 30 min per L2)
  inactivityTimeout: 900, // 15 min di inattività
  requireReauth: true     // operazioni di firma richiedono nuovo login
};

// Logout corretto: Single Logout (SLO) obbligatorio per SPID
async function handleLogout(req: Request, res: Response) {
  const session = req.session;

  // 1. Costruisci logout request per l'IdP
  const logoutRequest = createSignedLogoutRequest({
    iss: "https://my-service.comune.it",
    sub: session.userId,
    sid: session.idTokenHint,  // session ID dal ID Token
    aud: session.idpLogoutEndpoint,
    post_logout_redirect_uri: "https://my-service.comune.it/logout-complete",
  });

  // 2. Distruggi la sessione locale prima del redirect
  await req.session.destroy();

  // 3. Redirect all'IdP per SLO
  res.redirect(
    `${session.idpLogoutEndpoint}?` +
    `id_token_hint=${session.idToken}&` +
    `post_logout_redirect_uri=https://my-service.comune.it/logout-complete&` +
    `state=${generateState()}`
  );
}

Accreditatie en naleving van AgID

Om SPID in zijn dienst te integreren, moet de Vertrouwende Partij het AgID-accreditatieproces doorlopen. De vereenvoudigde procedure voor OIDC is beschikbaar vanaf 2023:

  1. Technische registratie: Entiteitsconfiguratieconfiguratie en eindpuntverificatie /.well-known/openid-federation. AgID valideert de naleving via automatische tools.
  2. Functionele testen: Gebruik van spid-sp-test (open source Python-tool) om de juiste implementatie te verifiëren. De tool simuleert het gedrag van Identity Providers en verifieert ruim 200 testcases.
  3. Naleving van de AVG: documentatie van gegevensverwerking voor elk vereist SPID-attribuut.
  4. Ondertekening van de overeenkomst: aanvaarding van het SPID Technisch Reglement en de lidmaatschapsovereenkomst.
## Uso di spid-sp-test per verificare l'implementazione

# Installazione
pip install spid_sp_test

# Verifica metadata OIDC
spid_sp_test \
  --metadata-url https://my-service.comune.it/.well-known/openid-federation \
  --profile oidc_op

# Test flusso completo (richiede credenziali di test)
spid_sp_test \
  --metadata-url https://my-service.comune.it/.well-known/openid-federation \
  --authn-url https://my-service.comune.it/login/spid \
  --profile oidc_sp_public \
  --extra \
  --debug

# Output: report HTML con dettagli dei 200+ test
# File: report_<timestamp>.html

Op weg naar convergentie: SPID/CIE OIDC en EUDI Wallet

De transitie van SAML naar OIDC is niet de eindbestemming. Het ecosysteem evolueert richting volledige integratie met EUDI-portemonnee via OpenID4VP. Het project iam-proxy-italia op GitHub demonstreert het al hoe een IAM-proxy tegelijkertijd SAML 2.0, OIDC en OpenID4VC (voor IT-Wallet/EUDI Wallet) kan ondersteunen.

De ideale architectuur voor een moderne PA-dienst moet alle drie de stromen transparant ondersteunen:

// Middleware di autenticazione multi-protocollo

interface AuthProvider {
  protocol: "saml2" | "oidc" | "openid4vp";
  name: string;
  endpoint: string;
}

const AUTH_PROVIDERS: AuthProvider[] = [
  {
    protocol: "oidc",
    name: "SPID",
    endpoint: "https://registry.spid.gov.it"
  },
  {
    protocol: "oidc",
    name: "CIE",
    endpoint: "https://idserver.servizicie.interno.gov.it"
  },
  {
    protocol: "openid4vp",
    name: "IT Wallet / EUDI Wallet",
    endpoint: "eudi-openid4vp://"
  }
];

function selectAuthFlow(userAgent: string, userPreference?: string): AuthProvider {
  // Logica di selezione basata su contesto
  if (userPreference === "wallet" || hasWalletApp(userAgent)) {
    return AUTH_PROVIDERS.find(p => p.protocol === "openid4vp")!;
  }
  // Default: CIE OIDC (LoA3 per impostazione predefinita)
  return AUTH_PROVIDERS.find(p => p.name === "CIE")!;
}

Beste praktijken en antipatronen

Best practices voor SPID/CIE OIDC

  • Gebruik altijd PKCE, zelfs voor webapplicaties (verplicht voor native apps)
  • Implementeer JARM om de autorisatiereactie te beschermen
  • Roteer RP-ondertekeningssleutels elke 12 maanden (of vaker)
  • VS sub paarsgewijze als interne identificatie — nooit attributen zoals CF of e-mail
  • Implementeer Single Logout (SLO) — verplicht voor SPID-accreditatie
  • Cache de vertrouwensketen gedurende maximaal 24 uur (en update vervolgens)
  • Controleer de vervaldatum van de Entiteitsverklaring (doorgaans 24 uur)

Antipatronen die u moet vermijden

  • Sla het ID-token niet op localStorage of niet-HttpOnly-cookies
  • Niet gebruiken response_mode=query (VS form_post o form_post.jwt)
  • Stel het toegangstoken niet zonder geldige reden bloot aan de frontend
  • Vraag geen onnodige SPID-attributen aan (accreditatierisico)
  • Negeer de verificatie van nonce in de Token-ID
  • Gebruik geen niet-aflopende sessies: PA-sessies moeten time-outs hebben

Conclusies

OpenID Connect vertegenwoordigt de toekomst van Italiaanse en Europese overheidsauthenticatie. De transitie van SAML naar OIDC het is niet alleen technisch; het brengt een moderner ecosysteem, betere hulpmiddelen en een natuurlijk pad met zich mee naar een zelf-soevereine identiteit met de EUDI Wallet.

Ontwikkelaars gaan vandaag SPID/CIE OIDC implementeren met de juiste patronen: PKCE, JARM, automatische federatie, paarsgewijs onderwerp - bevinden zich in een optimale positie voor latere integratie met OpenID4VP en de EUDI Wallet. Code die vandaag goed is geschreven, hoeft morgen niet te worden herschreven: het is een investering in de continuïteit van publieke digitale diensten.

De GovTech-serie gaat verder

Ontdek in het volgende artikel het ontwerp van Open Data API's en hoe u openbare gegevens efficiënt kunt publiceren en gebruiken.