Evoluce směrem k OIDC v italské vládní identitě

Již více než deset let je italská digitální identita založena na protokolu SAML 2.0: robustní, dobře standardizovaný, ale navržený v předmobilní době. S explozí nativních aplikací pro chytré telefony a rozšířením jednostránkových aplikací SAML ukázal svá omezení — výměna objemného XML, špatná mobilní podpora, složitá integrace do SPA.

Přechod na OpenID Connect (OIDC) bylo to nevyhnutelné. V lednu 2023 zveřejnila společnost AgID Pokyny pro připojení OpenID v SPID e le Pokyny pro připojení OpenID v CIE, čímž se standard založený na OAuth 2.0 stává oficiálním jako cesta evoluce identity italské vlády. Cíl PNRR vyžaduje, aby do března 2026 všech 16 500 italských PA přijalo elektronickou identifikaci prostřednictvím SPID nebo CIE.

Proč OpenID Connect vs SAML pro PA

  • Lehké žetony: JWT místo tvrzení XML SAML – užitečné zatížení sníženo o 60–70 %
  • Mobilní první: OAuth 2.0 s PKCE navržený pro nativní aplikace a SPA
  • Moderní ekosystém: knihovny dostupné pro jakýkoli jazyk/rámec
  • Standardizovaná federace: OpenID Federation 1.0 pro automatický řetězec důvěry
  • Směrem k peněžence EUDI: OpenID4VP a OpenID4VCI používají stejná primitiva OIDC

Architektura SPID/CIE OIDC federace

OIDC federace pro SPID a CIE je založena na OpenID Federation 1.0, standard, který automatizuje stavbu řetězu důvěra mezi subjekty. To překonává manuální model registrace metadat SAML, kde si každý SP musel ručně vyměňovat metadata XML s každým IdP.

Subjekty ve Federaci

  • Trust Anchor (TA): AgID je důvěryhodnou kotvou italské federace. Publikuje svou konfiguraci entity do známého koncového bodu.
  • Střední: AgID autorizované agregátory, které sdružují více RP (např. regionální PA, obecní agregátory).
  • Spoléhající se strana (RP): aplikace, která ověřuje uživatele prostřednictvím SPID/CIE OIDC.
  • Poskytovatel identity (OP): Poskytovatelé SPID (Poste Italiane, Aruba, InfoCert atd.) nebo CIE IdP Ministerstva vnitra.

Konfigurace entity a řetězec důvěryhodnosti

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

Implementujte tok autorizačního kódu pomocí PKCE

Doporučený průtok pro SPID/CIE OIDC jeTok autorizačního kódu s PKCE (Proof Key for Code Exchange, RFC 7636). PKCE chrání před zachycenými autorizačními kódy a je povinný pro mobilní aplikace a doporučený pro webové aplikace.

Krok 1: Žádost o generování a autorizaci PKCE

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

Krok 2: Zpětné volání a výměna tokenů

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

Úrovně jistoty (LoA) ve SPID a CIE

Jedním z nejdůležitějších aspektů vládní identity je Úroveň jistoty (LoA), což ukazuje, jak spolehlivý je proces ověřování. SPID definuje tři úrovně:

Úroveň URI ACR Metoda Typické použití
SpidL1 https://www.spid.gov.it/SpidL1 Uživatelské jméno + heslo Služby s nízkým rizikem, konzultace s necitlivými daty
SpidL2 https://www.spid.gov.it/SpidL2 Uživatelské jméno + heslo + OTP/push Standardní služby PA (deklarace, žádosti, platby)
SpidL3 https://www.spid.gov.it/SpidL3 Certifikované fyzické zařízení Vysoce rizikové operace, kvalifikovaný elektronický podpis

Pro CIE (elektronická identifikační karta), úroveň jistoty je vždy LoA3 (maximum), když přijde používá čip CIE NFC, protože vyžaduje fyzické držení dokumentu a PIN. Díky tomu je CIE jasnou volbou pro služby, které vyžadují silnou autentizaci.

UserInfo Endpoint a atributy SPID/CIE

Po přihlášení může spoléhající strana načíst atributy uživatele z koncového bodu UserInfo pomocí přístupového tokenu. Atributy dostupné v SPID OIDC jsou definovány v technickém profilu:

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

Ochrana osobních údajů a minimalizace atributů SPID

Vyžadování daňového řádu uživatele bez skutečné potřeby je porušením principu minimalizace GDPR. Směrnice AgID specifikují, že každý požadovaný atribut musí být motivován konkrétní funkcí služby, např uvedeno v zásadách ochrany osobních údajů. Během auditu AgID může dojít k selhání zdůvodnění požadovaných atributů vést ke zrušení akreditace.

SDK a oficiální knihovny pro SPID/CIE OIDC

Projekt Vývojáři Itálie (developers.italia.it) poskytuje oficiální sady SDK v různých jazycích pro zjednodušení integrace 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: Režim autorizační odpovědi založený na JWT

Směrnice SPID/CIE OIDC doporučují použití JARM (režim autorizační odpovědi JWT) chránit autorizační odpověď. Místo procházení code e state jako prostý text jako parametry dotazu, JARM zapouzdří odpověď do JWT podepsaného poskytovatelem identity.

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

Správa relace a obnovení tokenu

Kritickým aspektem identity vlády je správné řízení relací. Na rozdíl od soukromého sektoru, Služby PA musí respektovat přísná omezení týkající se trvání relace a odhlášení.

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

Akreditace a soulad s AgID

Pro integraci SPID do své služby musí Spoléhající strana projít procesem akreditace AgID. Zjednodušený postup pro OIDC je k dispozici od roku 2023:

  1. Technická registrace: Konfigurace konfigurace entity a ověření koncového bodu /.well-known/openid-federation. AgID ověřuje shodu prostřednictvím automatických nástrojů.
  2. Funkční testy: Použití spid-sp-test (open source Python nástroj) k ověření správné implementace. Nástroj simuluje chování poskytovatelů identity a ověřuje více než 200 testovacích případů.
  3. Soulad s GDPR: dokumentace zpracování dat pro každý požadovaný atribut SPID.
  4. Podpis smlouvy: přijetí Technických pravidel SPID a smlouvy o členství.
## 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

Směrem ke konvergenci: SPID/CIE OIDC a EUDI Wallet

Přechod ze SAML na OIDC není konečným cílem. Ekosystém směřuje k úplné integraci s EUDI peněženka přes OpenID4VP. Projekt iam-proxy-italia na GitHubu již ukazuje jak IAM proxy může současně podporovat SAML 2.0, OIDC a OpenID4VC (pro IT-Wallet/EUDI Wallet).

Ideální architektura pro moderní službu PA by měla transparentně podporovat všechny tři toky:

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

Osvědčené postupy a anti-vzorce

Nejlepší postupy pro SPID/CIE OIDC

  • Vždy používat PKCE i pro webové aplikace (povinné pro nativní aplikace)
  • Implementujte JARM pro ochranu autorizační odpovědi
  • Střídejte podpisové klíče RP každých 12 měsíců (nebo častěji)
  • USA sub pairwise jako interní identifikátor – nikdy atributy jako CF nebo email
  • Implementujte jednotné odhlášení (SLO) — povinné pro akreditaci SPID
  • Uložte řetězec důvěryhodnosti do mezipaměti až na 24 hodin (poté aktualizujte)
  • Sledujte vypršení platnosti prohlášení entity (obvykle 24 hodin)

Anti-vzory, kterým je třeba se vyhnout

  • Neukládejte ID token localStorage nebo soubory cookie jiné než HTTP
  • Nepoužívejte response_mode=query (USA form_post o form_post.jwt)
  • Bez platného důvodu nevystavujte přístupový token frontendu
  • Nevyžadujte zbytečné atributy SPID (akreditační riziko)
  • Neignorujte ověření nonce v Token ID
  • Nepoužívejte relace, které nevyprší: Relace PA musí mít časové limity

Závěry

OpenID Connect představuje budoucnost italské a evropské vládní autentizace. Přechod od SAML k OIDC není jen technický – přináší s sebou modernější ekosystém, lepší nástroje a přirozenou cestu směrem k vlastní suverénní identitě s peněženkou EUDI.

Vývojáři začínají dnes implementovat SPID/CIE OIDC se správnými vzory – PKCE, JARM, automatická federace, pairwise subject — jsou v optimální pozici pro následnou integraci s OpenID4VP a EUDI Wallet. Dnes dobře napsaný kód nemusí být zítra přepisován: je to investice do kontinuity veřejných digitálních služeb.

Série GovTech pokračuje

Prozkoumejte design Open Data API a jak efektivně publikovat a využívat veřejná data v dalším článku.