Ewolucja w kierunku OIDC we włoskiej tożsamości rządowej

Od ponad dekady włoska tożsamość cyfrowa opiera się na protokole SAML 2.0: solidny, dobrze ustandaryzowany, ale zaprojektowany w erze przed mobilną. Wraz z eksplozją natywnych aplikacji na smartfony i rozpowszechnieniem aplikacji jednostronicowych, SAML pokazał swoje ograniczenia — wymiana nieporęcznych plików XML, słaba obsługa mobilna, złożona integracja z SPA.

Przejście do Połączenie OpenID (OIDC) było to nieuniknione. W styczniu 2023 r. AgID opublikował Wytyczne OpenID Connect w SPID e le Wytyczne OpenID Connect w CIE, czyniąc standard oparty na OAuth 2.0 oficjalnym jako ścieżkę ewolucji tożsamości włoskiego rządu. Cel PNRR wymaga, aby do marca 2026 r. wszystkie 16 500 włoskich agencji płatniczych przyjęły identyfikację elektroniczną za pośrednictwem SPID lub CIE.

Dlaczego OpenID Connect vs SAML dla PA

  • Lekkie tokeny: JWT zamiast asercji XML SAML — ładowność zmniejszona o 60-70%
  • Najpierw mobilnie: OAuth 2.0 z PKCE przeznaczony dla aplikacji natywnych i SPA
  • Nowoczesny ekosystem: biblioteki dostępne dla dowolnego języka/frameworka
  • Standaryzowana federacja: OpenID Federation 1.0 dla automatycznego łańcucha zaufania
  • W stronę portfela EUDI: OpenID4VP i OpenID4VCI używają tych samych prymitywów OIDC

Architektura Federacji SPID/CIE OIDC

Na jej podstawie opiera się federacja OIDC dla SPID i CIE Federacja OpenID 1.0, standard automatyzujący budowę łańcucha zaufanie między podmiotami. To przewyższa ręczny model rejestracji metadanych SAML, w którym każdy dostawca usług musiał ręcznie wymieniać metadane XML z każdym dostawcą tożsamości.

Podmioty w Federacji

  • Kotwica zaufania (TA): AgID jest kotwicą zaufania włoskiej federacji. Publikuje konfigurację jednostki w dobrze znanym punkcie końcowym.
  • Mediator: autoryzowani agregatorzy AgID, którzy łączą wiele RP (np. regionalne agencje płatnicze, agregatorzy gminni).
  • Strona uzależniona (RP): aplikacja uwierzytelniająca użytkowników poprzez SPID/CIE OIDC.
  • Dostawca tożsamości (OP): dostawcy SPID (Poste Italiane, Aruba, InfoCert itp.) lub CIE IdP Ministerstwa Spraw Wewnętrznych.

Konfiguracja jednostki i łańcuch zaufania

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

Wdrażaj przepływ kodów autoryzacyjnych w PKCE

Zalecany przepływ dla SPID/CIE OIDC toPrzepływ kodu autoryzacyjnego z PKCE (Klucz próbny do wymiany kodów, RFC 7636). PKCE chroni przed przechwyconymi kodami autoryzacyjnymi i jest obowiązkowe w przypadku aplikacji mobilnych i zalecane w przypadku aplikacji internetowych.

Krok 1: Żądanie wygenerowania i autoryzacji 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: Oddzwonienie i wymiana tokenów

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

Poziomy pewności (LoA) w SPID i CIE

Jednym z najważniejszych aspektów tożsamości rządu jest Poziom pewności (LoA), co wskazuje, jak niezawodny jest proces uwierzytelniania. SPID definiuje trzy poziomy:

Poziom URI ACR Metoda Typowe zastosowanie
SpidL1 https://www.spid.gov.it/SpidL1 Nazwa użytkownika + hasło Usługi niskiego ryzyka, konsultacje dotyczące danych niewrażliwych
SpidL2 https://www.spid.gov.it/SpidL2 Nazwa użytkownika + hasło + OTP/push Standardowe usługi PA (deklaracje, wnioski, płatności)
SpidL3 https://www.spid.gov.it/SpidL3 Certyfikowane urządzenie fizyczne Operacje wysokiego ryzyka, kwalifikowany podpis elektroniczny

Dla CIE (elektroniczny dowód tożsamości), poziom pewności to zawsze LoA3 (maksymalny), jeśli chodzi użył chipa CIE NFC, gdyż wymaga to fizycznego posiadania dokumentu i PIN-u. To sprawia, że CIE jest oczywistym wyborem dla usług wymagających silnego uwierzytelniania.

Punkt końcowy UserInfo i atrybuty SPID/CIE

Po zalogowaniu Strona Uzależniona może pobrać atrybuty użytkownika z Punktu Końcowego UserInfo za pomocą Tokena Dostępu. Atrybuty dostępne w SPID OIDC zdefiniowane są w profilu technicznym:

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

Prywatność i minimalizacja atrybutów SPID

Żądanie kodu podatkowego użytkownika bez rzeczywistej potrzeby stanowi naruszenie zasady minimalizacji RODO. Wytyczne AgID określają, że każdy żądany atrybut musi być motywowany konkretną funkcjonalnością usługi, np wskazane w polityce prywatności. Podczas audytu AgID może wystąpić brak uzasadnienia żądanych atrybutów prowadzić do cofnięcia akredytacji.

SDK i oficjalne biblioteki dla SPID/CIE OIDC

Projekt Deweloperzy Włochy (developers.italia.it) udostępnia oficjalne pakiety SDK w różnych językach aby uprościć integrację 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: Tryb odpowiedzi autoryzacyjnej oparty na JWT

Wytyczne SPID/CIE OIDC zalecają stosowanie JARM (tryb odpowiedzi autoryzacyjnej JWT) chronić odpowiedź autoryzacyjną. Zamiast przejść code e state w postaci zwykłego tekstu jako parametry zapytania, JARM hermetyzuje odpowiedź w JWT podpisanym przez dostawcę tożsamości.

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

Zarządzanie sesjami i odświeżanie tokenów

Krytycznym aspektem tożsamości rządu jest prawidłowe zarządzanie sesjami. W odróżnieniu od sektora prywatnego, Usługi PA muszą przestrzegać rygorystycznych ograniczeń dotyczących czasu trwania sesji i wylogowania.

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

Akredytacja i zgodność z AgID

Aby zintegrować SPID ze swoją usługą, Strona Ufająca musi przejść proces akredytacji AgID. Od 2023 r. dostępna będzie uproszczona procedura OIDC:

  1. Rejestracja techniczna: Konfiguracja konfiguracji jednostki i weryfikacja punktu końcowego /.well-known/openid-federation. AgID sprawdza zgodność za pomocą automatycznych narzędzi.
  2. Testy funkcjonalne: Użycie spid-sp-test (narzędzie Pythona typu open source) w celu sprawdzenia poprawności implementacji. Narzędzie symuluje zachowanie Dostawców Tożsamości i weryfikuje ponad 200 przypadków testowych.
  3. Zgodność z RODO: dokumentacja przetwarzania danych dla każdego wymaganego atrybutu SPID.
  4. Podpisanie umowy: akceptacja Regulaminu Technicznego SPID i umowy członkowskiej.
## 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

W stronę konwergencji: portfel SPID/CIE OIDC i EUDI

Przejście z SAML na OIDC nie jest ostatecznym celem. Ekosystem zmierza w stronę całkowitej integracji z Portfel EUDI poprzez OpenID4VP. Projekt iam-proxy-italia na GitHub już demonstruje w jaki sposób serwer proxy IAM może jednocześnie obsługiwać SAML 2.0, OIDC i OpenID4VC (dla portfela IT/EUDI Wallet).

Idealna architektura nowoczesnej usługi PA powinna w przejrzysty sposób obsługiwać wszystkie trzy przepływy:

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

Najlepsze praktyki i anty-wzorce

Najlepsze praktyki dla SPID/CIE OIDC

  • Zawsze używaj PKCE nawet w przypadku aplikacji internetowych (obowiązkowe w przypadku aplikacji natywnych)
  • Zaimplementuj JARM, aby chronić odpowiedź autoryzacyjną
  • Rotuj klucze podpisywania RP co 12 miesięcy (lub częściej)
  • USA sub parami jako identyfikator wewnętrzny — nigdy atrybuty takie jak CF lub e-mail
  • Wdrożenie pojedynczego wylogowania (SLO) — obowiązkowe w przypadku akredytacji SPID
  • Buforuj łańcuch zaufania przez maksymalnie 24 godziny (następnie zaktualizuj)
  • Monitoruj wygaśnięcie wyciągu z jednostki (zazwyczaj 24h)

Anty-wzorce, których należy unikać

  • Nie zapisuj tokena identyfikacyjnego w localStorage lub pliki cookie inne niż HttpOnly
  • Nie używać response_mode=query (USA form_post o form_post.jwt)
  • Nie udostępniaj tokenu dostępu frontendowi bez ważnego powodu
  • Nie żądaj niepotrzebnych atrybutów SPID (ryzyko akredytacji)
  • Nie ignoruj ​​weryfikacji nonce w identyfikatorze tokena
  • Nie używaj sesji, które nie wygasają: sesje PA muszą mieć limity czasu

Wnioski

OpenID Connect reprezentuje przyszłość uwierzytelniania rządów włoskich i europejskich. Przejście z SAML na OIDC to nie tylko kwestia techniczna — niesie ze sobą bardziej nowoczesny ekosystem, lepsze narzędzia i naturalną ścieżkę w kierunku suwerennej tożsamości z portfelem EUDI.

Programiści rozpoczynający dziś wdrażanie SPID/CIE OIDC z poprawnymi wzorcami — PKCE, JARM, automatyczna federacja, Temat w parach — znajdują się w optymalnej pozycji do późniejszej integracji z OpenID4VP i portfelem EUDI. Kod napisany dzisiaj dobrze nie wymaga jutro pisania od nowa: to inwestycja w ciągłość publicznych usług cyfrowych.

Seria GovTech trwa

W następnym artykule poznasz konstrukcję interfejsów API Open Data oraz sposoby efektywnego publikowania i wykorzystywania danych publicznych.