Salta al contenuto principale

OpenID Connect per Identità Governativa

SPID e CIE si evolvono verso OpenID Connect: come implementare un Relying Party per i sistemi di identità italiana, configurare la federazione OIDC…

Cosa ne pensi di questo articolo?

In Breve

SPID e CIE si evolvono verso OpenID Connect: come implementare un Relying Party per i sistemi di identità italiana, configurare la federazione OIDC…

GovTech 25 min

OpenID Connect per Identità Governativa

SPID e CIE si evolvono verso OpenID Connect: come implementare un Relying Party per i sistemi di identità italiana, configurare la federazione OIDC, gestire i livelli di garanzia (LoA), proteggere i flussi con PKCE e JARM, e prepararsi alla convergenza con l’ecosistema EUDI Wallet.

OpenID Connect OIDC SPID CIE Identity Federation Government Identity

L’Evoluzione verso OIDC nell’Identità Governativa Italiana

Per oltre un decennio, l’identità digitale italiana si è basata sul protocollo SAML 2.0: robusto, ben standardizzato, ma progettato in un’era pre-mobile. Con l’esplosione delle applicazioni native per smartphone e la diffusione delle Single Page Application, SAML ha mostrato i suoi limiti - scambio di XML voluminosi, scarso supporto al mobile, integrazione complessa nelle SPA.

La transizione verso OpenID Connect (OIDC) era inevitabile. Nel gennaio 2023, AgID ha pubblicato le Linee Guida OpenID Connect in SPID e le Linee Guida OpenID Connect in CIE, rendendo ufficiale lo standard basato su OAuth 2.0 come percorso di evoluzione dell’identità governativa italiana. Il target PNRR richiede che entro marzo 2026 tutte le 16.500 PA italiane adottino identificazione elettronica via SPID o CIE.

Perché OpenID Connect vs SAML per la PA

  • Token leggeri: JWT invece di XML SAML assertion - payload ridotto del 60-70%
  • Mobile-first: OAuth 2.0 con PKCE pensato per app native e SPA
  • Ecosistema moderno: librerie disponibili per qualsiasi linguaggio/framework
  • Federazione standardizzata: OpenID Federation 1.0 per la trust chain automatica
  • Verso EUDI Wallet: OpenID4VP e OpenID4VCI usano gli stessi primitivi OIDC

Architettura della Federazione SPID/CIE OIDC

La federazione OIDC per SPID e CIE si basa su OpenID Federation 1.0, uno standard che automatizza la costruzione della catena di fiducia tra entità. Questo supera il modello manuale di registrazione dei metadata SAML, dove ogni SP doveva scambiare manualmente i metadata XML con ogni IdP.

Entità nella Federazione

  • Trust Anchor (TA): AgID à il trust anchor della federazione italiana. Pubblica il suo Entity Configuration al well-known endpoint.
  • Intermediate: Aggregatori autorizzati da AgID che federano più RP (ad es. PA regionali, aggregatori di Comuni).
  • Relying Party (RP): l’applicazione che autentica gli utenti tramite SPID/CIE OIDC.
  • Identity Provider (OP): i provider SPID (Poste Italiane, Aruba, InfoCert, ecc.) o il CIE IdP del Ministero dell’Interno.

Entity Configuration e Trust Chain

// 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"
  ]
}

Implementare il Flusso Authorization Code con PKCE

Il flusso raccomandato per SPID/CIE OIDC è l’Authorization Code Flow con PKCE (Proof Key for Code Exchange, RFC 7636). PKCE protegge dai codici di autorizzazione intercettati, ed è obbligatorio per le app mobile e raccomandato per le web app.

Step 1: Generazione PKCE e Authorization Request

// 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()}`);
}

Step 2: Callback e Token Exchange

// 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);
}

Livelli di Garanzia (LoA) in SPID e CIE

Uno degli aspetti più importanti dell’identità governativa è il Livello di Garanzia (Level of Assurance, LoA), che indica quanto è affidabile il processo di autenticazione. SPID definisce tre livelli:

Livello ACR URI Metodo Uso Tipico
SpidL1 https://www.spid.gov.it/SpidL1 Username + password Servizi a basso rischio, consultazione dati non sensibili
SpidL2 https://www.spid.gov.it/SpidL2 Username + password + OTP/push Servizi standard PA (dichiarazioni, domande, pagamenti)
SpidL3 https://www.spid.gov.it/SpidL3 Dispositivo fisico certificato Operazioni ad alto rischio, firma elettronica qualificata

Per CIE (Carta d’Identità Elettronica), il livello di garanzia è sempre LoA3 (il massimo) quando viene usato il chip NFC della CIE, poiché richiede il possesso fisico del documento e il PIN. Questo rende CIE la scelta obbligata per i servizi che richiedono autenticazione forte.

UserInfo Endpoint e Attributi SPID/CIE

Dopo il login, il Relying Party può recuperare gli attributi dell’utente dall’UserInfo Endpoint usando l’Access Token. Gli attributi disponibili in SPID OIDC sono definiti nel profilo tecnico:

// 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 e Minimizzazione degli Attributi SPID

Richiedere il codice fiscale dell’utente senza una reale necessità è una violazione del principio GDPR di minimizzazione. Le Linee Guida AgID specificano che ogni attributo richiesto deve essere motivato dalla specifica funzionalità del servizio e indicato nella informativa privacy. In sede di audit AgID, la mancata giustificazione degli attributi richiesti può portare alla revoca dell’accreditamento.

SDK e Librerie Ufficiali per SPID/CIE OIDC

Il progetto Developers Italia (developers.italia.it) mette a disposizione SDK ufficiali in diversi linguaggi per semplificare l’integrazione SPID/CIE OIDC:

## 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-Based Authorization Response Mode

Le Linee Guida SPID/CIE OIDC raccomandano l’uso di JARM (JWT Authorization Response Mode) per proteggere la risposta di autorizzazione. Invece di passare code e state in chiaro come query parameters, JARM incapsula la risposta in un JWT firmato dall’Identity Provider.

// 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

Gestione delle Sessioni e Token Refresh

Un aspetto critico nell’identità governativa è la gestione corretta delle sessioni. A differenza del settore privato, i servizi PA devono rispettare vincoli stringenti su durata delle sessioni e logout.

// 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()}`
  );
}

Accreditamento e Compliance con AgID

Per integrare SPID nel proprio servizio, il Relying Party deve superare il processo di accreditamento AgID. Dal 2023 è disponibile la procedura semplificata per OIDC:

  1. Registrazione tecnica: configurazione dell’Entity Configuration e verifica dell’endpoint /.well-known/openid-federation. AgID valida la conformità tramite tool automatici.
  2. Test funzionali: utilizzo di spid-sp-test (tool Python open source) per verificare la corretta implementazione. Il tool simula il comportamento degli Identity Provider e verifica oltre 200 test case.
  3. Conformità GDPR: documentazione del trattamento dati per ogni attributo SPID richiesto.
  4. Firma del accordo: accettazione delle Regole Tecniche SPID e dell’accordo di adesione.
## 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

Verso la Convergenza: SPID/CIE OIDC e EUDI Wallet

La transizione da SAML a OIDC non è la destinazione finale. L’ecosistema si sta muovendo verso la completa integrazione con EUDI Wallet attraverso OpenID4VP. Il progetto iam-proxy-italia su GitHub già dimostra come un proxy IAM possa supportare contemporaneamente SAML 2.0, OIDC e OpenID4VC (per IT-Wallet/EUDI Wallet).

L’architettura ideale per un servizio PA moderno dovrebbe supportare tutti e tre i flussi in modo trasparente:

// 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")!;
}

Best Practices e Anti-Pattern

Best Practices per SPID/CIE OIDC

  • Usa sempre PKCE anche per le web application (obbligatorio per app native)
  • Implementa JARM per proteggere la risposta di autorizzazione
  • Ruota le chiavi di firma del RP ogni 12 mesi (o più frequentemente)
  • Usa sub pairwise come identificatore interno - mai attributi come CF o email
  • Implementa il Single Logout (SLO) - obbligatorio per accreditamento SPID
  • Cachea la trust chain per massimo 24 ore (poi aggiorna)
  • Monitora la scadenza dell’Entity Statement (tipicamente 24h)

Anti-Pattern da Evitare

  • Non salvare l’ID Token in localStorage o cookie non HttpOnly
  • Non usare response_mode=query (usa form_post o form_post.jwt)
  • Non esporre l’Access Token al frontend senza valida motivazione
  • Non richiedere attributi SPID non necessari (rischio accreditamento)
  • Non ignorare la verifica del nonce nell’ID Token
  • Non usare sessioni senza scadenza: le sessioni PA devono avere timeout

Conclusioni

OpenID Connect rappresenta il futuro dell’autenticazione governativa italiana ed europea. La transizione da SAML a OIDC non è solo tecnica - porta con sé un ecosistema più moderno, strumenti migliori e un percorso naturale verso l’identità self-sovereign con l’EUDI Wallet.

I developer che iniziano oggi ad implementare SPID/CIE OIDC con i pattern corretti - PKCE, JARM, federazione automatica, pairwise subject - si trovano in una posizione ottimale per la successiva integrazione con OpenID4VP e l’EUDI Wallet. Il codice scritto bene oggi non va riscritto domani: è un investimento nella continuità dei servizi digitali pubblici.

Continua la serie GovTech

Esplora il design delle Open Data API e come pubblicare e consumare dati pubblici in modo efficiente nel prossimo articolo.

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti