OpenID Connect pentru identitate guvernamentală
SPID și CIE evoluează către OpenID Connect: cum să implementați o parte de încredere pentru sistemele de identitate italiene, configurați federația OIDC, gestionați nivelurile de asigurare (LoA), securizați fluxurile cu PKCE și JARM și pregătiți pentru convergența cu ecosistemul EUDI Wallet.
Evoluția către OIDC în identitatea guvernamentală italiană
De peste un deceniu, identitatea digitală italiană s-a bazat pe protocol SAML 2.0: robust, bine standardizat, dar concepute într-o eră pre-mobile. Odată cu explozia aplicațiilor native pentru smartphone-uri și răspândirea aplicațiilor cu o singură pagină, SAML și-a arătat limitările — schimbul de XML voluminos, suport mobil slab, integrare complexă în SPA-uri.
Trecerea la OpenID Connect (OIDC) era inevitabil. În ianuarie 2023, AgID a publicat Reguli OpenID Connect în SPID iar cel Reguli OpenID Connect în CIE, făcând oficial standardul bazat pe OAuth 2.0 ca o cale de evoluție a identității guvernului italian. Ținta PNRR impune ca până în martie 2026 toate cele 16.500 de AP-uri italiene să adopte identificarea electronică prin SPID sau CIE.
De ce OpenID Connect vs SAML pentru PA
- Jetoane ușoare: JWT în loc de afirmația XML SAML — sarcina utilă redusă cu 60-70%
- Mobil-în primul rând: OAuth 2.0 cu PKCE conceput pentru aplicații native și SPA-uri
- Ecosistem modern: biblioteci disponibile pentru orice limbă/cadru
- Federație standardizată: OpenID Federation 1.0 pentru lanțul automat de încredere
- Spre portofelul EUDI: OpenID4VP și OpenID4VCI folosesc aceleași primitive OIDC
Arhitectura Federației SPID/CIE OIDC
Federația OIDC pentru SPID și CIE are la bază OpenID Federation 1.0, un standard care automatizează construcția lanțului încredere între entităţi. Acest lucru depășește modelul manual de înregistrare a metadatelor SAML, în care fiecare SP trebuia să schimbe manual metadate XML cu fiecare IdP.
Entități din Federație
- Ancora de încredere (TA): AgID este ancora de încredere a federației italiene. Își publică configurația de entitate la punctul final bine-cunoscut.
- Intermediar: agregatori autorizați AgID care federază mai multe RP (de exemplu, AP regionale, agregatori municipali).
- Parte de încredere (RP): aplicația care autentifică utilizatorii prin SPID/CIE OIDC.
- Furnizor de identitate (OP): furnizorii SPID (Poste Italiane, Aruba, InfoCert etc.) sau CIE IdP al Ministerului de Interne.
Configurarea entității și lanțul de încredere
// 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"
]
}
Implementați fluxul de cod de autorizare cu PKCE
Debitul recomandat pentru SPID/CIE OIDC esteCod de autorizare Flux cu PKCE (Proof Key for Code Exchange, RFC 7636). PKCE protejează împotriva codurilor de autorizare interceptate și este obligatoriu pentru aplicațiile mobile și recomandat pentru aplicațiile web.
Pasul 1: Generarea PKCE și cererea de autorizare
// 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()}`);
}
Pasul 2: Reapelare și schimb de jetoane
// 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);
}
Niveluri de asigurare (LoA) în SPID și CIE
Unul dintre cele mai importante aspecte ale identității guvernamentale este Nivel de asigurare (LoA), ceea ce indică cât de fiabil este procesul de autentificare. SPID definește trei niveluri:
| Nivel | URI ACR | Metodă | Utilizare tipică |
|---|---|---|---|
| SpidL1 | https://www.spid.gov.it/SpidL1 | Nume utilizator + parola | Servicii cu risc scăzut, consultarea datelor nesensibile |
| SpidL2 | https://www.spid.gov.it/SpidL2 | Nume utilizator + parolă + OTP/push | Servicii standard PA (declarații, cereri, plăți) |
| SpidL3 | https://www.spid.gov.it/SpidL3 | Dispozitiv fizic certificat | Operațiuni cu risc ridicat, semnătură electronică calificată |
Pentru CIE (Cartea electronică de identitate), nivelul de asigurare este întotdeauna LoA3 (maximum) atunci când vine vorba a folosit cipul CIE NFC, deoarece necesită posesia fizică a documentului și a codului PIN. Acest lucru face ca CIE să fie alegerea evidentă pentru servicii care necesită autentificare puternică.
UserInfo Endpoint și atribute SPID/CIE
După conectare, partea de încredere poate prelua atributele utilizatorului de la punctul final UserInfo folosind Token-ul de acces. Atributele disponibile în SPID OIDC sunt definite în profilul tehnic:
// 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
// }
// }
Confidențialitate și minimizarea atributelor SPID
Solicitarea codului fiscal al utilizatorului fără o nevoie reală reprezintă o încălcare a principiului minimizării GDPR. Ghidul AgID specifică că fiecare atribut solicitat trebuie să fie motivat de funcționalitatea specifică a serviciului e indicate în politica de confidențialitate. În timpul auditului AgID, nejustificarea atributelor solicitate poate duce la revocarea acreditării.
SDK și biblioteci oficiale pentru SPID/CIE OIDC
Proiectul Dezvoltatori Italia (developers.italia.it) oferă SDK-uri oficiale în diferite limbi pentru a simplifica integrarea 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: Mod de răspuns la autorizare bazat pe JWT
Ghidurile SPID/CIE OIDC recomandă utilizarea JARM (Mod de răspuns la autorizare JWT) a proteja
răspunsul de autorizare. În loc să treacă code e state în text clar ca parametri de interogare,
JARM încapsulează răspunsul într-un JWT semnat de furnizorul de identitate.
// 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
Gestionarea sesiunii și reîmprospătarea jetoanelor
Un aspect critic în identitatea guvernamentală este gestionarea corectă a sesiunilor. Spre deosebire de sectorul privat, Serviciile PA trebuie să respecte constrângeri stricte privind durata sesiunii și deconectare.
// 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()}`
);
}
Acreditare și conformitate cu AgID
Pentru a integra SPID în serviciul său, Partea de bază trebuie să treacă procesul de acreditare AgID. Procedura simplificată pentru OIDC este disponibilă din 2023:
-
Inregistrare tehnica: Configurarea Configurației entității și verificarea punctului final
/.well-known/openid-federation. AgID validează conformitatea prin instrumente automate. - Teste funcționale: Utilizarea spid-sp-test (instrument Python open source) pentru a verifica implementarea corectă. Instrumentul simulează comportamentul furnizorilor de identitate și verifică peste 200 de cazuri de testare.
- Conformitatea GDPR: documentarea procesării datelor pentru fiecare atribut SPID necesar.
- Semnarea acordului: acceptarea Regulilor tehnice SPID și a acordului de membru.
## 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
Spre convergență: SPID/CIE OIDC și EUDI Wallet
Tranziția de la SAML la OIDC nu este destinația finală. Ecosistemul se îndreaptă către o integrare completă cu Portofelul EUDI prin OpenID4VP. Proiectul iam-proxy-italia pe GitHub demonstrează deja cum un proxy IAM poate suporta simultan SAML 2.0, OIDC și OpenID4VC (pentru IT-Wallet/EUDI Wallet).
Arhitectura ideală pentru un serviciu modern de PA ar trebui să suporte toate cele trei fluxuri în mod transparent:
// 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")!;
}
Cele mai bune practici și anti-modele
Cele mai bune practici pentru SPID/CIE OIDC
- Utilizați întotdeauna PKCE chiar și pentru aplicații web (obligatoriu pentru aplicațiile native)
- Implementați JARM pentru a proteja răspunsul de autorizare
- Rotiți cheile de semnare RP la fiecare 12 luni (sau mai des)
- STATELE UNITE ALE AMERICII
subperechi ca identificator intern - niciodată atribute precum CF sau e-mail - Implementați deconectare unică (SLO) — obligatoriu pentru acreditarea SPID
- Memorați în cache lanțul de încredere timp de până la 24 de ore (apoi actualizați)
- Monitorizați expirarea Declarației de entitate (de obicei 24 de ore)
Anti-modele de evitat
- Nu salvați codul de identificare
localStoragesau cookie-uri non-HttpOnly - Nu utilizați
response_mode=query(STATELE UNITE ALE AMERICIIform_postoform_post.jwt) - Nu expuneți jetonul de acces la front-end fără un motiv întemeiat
- Nu solicitați atribute SPID inutile (risc de acreditare)
- Nu ignora verificarea
nonceîn ID-ul jetonului - Nu utilizați sesiuni care nu expiră: sesiunile PA trebuie să aibă expirări
Concluzii
OpenID Connect reprezintă viitorul autentificării guvernamentale italiene și europene. Trecerea de la SAML la OIDC nu este doar tehnic – aduce cu sine un ecosistem mai modern, instrumente mai bune și o cale naturală spre identitatea autosuverană cu portofelul EUDI.
Dezvoltatorii care încep astăzi să implementeze SPID/CIE OIDC cu modelele corecte — PKCE, JARM, federație automată, subiect perechi — sunt într-o poziție optimă pentru integrarea ulterioară cu OpenID4VP și portofelul EUDI. Codul scris bine astăzi nu trebuie rescris mâine: este o investiție în continuitatea serviciilor digitale publice.
Seria GovTech continuă
Explorați designul API-urilor Open Data și cum să publicați și să consumați eficient datele publice în următorul articol.







