イタリア政府のアイデンティティにおける OIDC への進化

10 年以上にわたり、イタリアのデジタル アイデンティティはプロトコルに基づいてきました。 SAML 2.0: 堅牢、十分に標準化されている、 しかし、モバイル以前の時代に設計されました。スマートフォン向けネイティブアプリの爆発的な普及とシングルページアプリの普及により、 SAML には、かさばる XML の交換、貧弱なモバイル サポート、SPA への複雑な統合など、その限界が明らかになりました。

への移行 OpenID Connect (OIDC) それは避けられないことだった。 2023 年 1 月に、AgID は SPID の OpenID Connect ガイドライン そして CIE の OpenID Connect ガイドライン、イタリア政府のアイデンティティの進化の道として、OAuth 2.0 に基づく標準を公式なものにしました。 PNRR の目標では、2026 年 3 月までにイタリアの 16,500 のすべての PA が SPID または CIE による電子 ID を採用することが求められています。

PA で OpenID Connect と SAML を比較する理由

  • 軽量トークン: XML SAML アサーションの代わりに JWT — ペイロードが 60 ~ 70% 削減
  • モバイルファースト: ネイティブ アプリと SPA 向けに設計された PKCE を使用した OAuth 2.0
  • 現代のエコシステム: あらゆる言語/フレームワークで利用可能なライブラリ
  • 標準化されたフェデレーション: 自動トラストチェーン用の OpenID Federation 1.0
  • EUDIウォレットに向けて: OpenID4VP と OpenID4VCI は同じ OIDC プリミティブを使用します

SPID/CIE OIDC フェデレーションのアーキテクチャ

SPID と CIE の OIDC フェデレーションは、以下に基づいています。 OpenID フェデレーション 1.0、チェーン構築を自動化する標準 エンティティ間の信頼。これは、各 SP が手動でメタデータを交換する必要があった SAML メタデータ登録の手動モデルを超えています。 各 IdP の XML。

連邦内のエンティティ

  • トラストアンカー(TA): AgID はイタリア連盟のトラストアンカーです。エンティティ構成を既知のエンドポイントに公開します。
  • 中級: 複数の RP を統合する AgID 認定アグリゲーター (地域 PA、自治体アグリゲーターなど)。
  • 依拠当事者 (RP): SPID/CIE OIDC 経由でユーザーを認証するアプリケーション。
  • アイデンティティプロバイダー (OP): SPID プロバイダー (Poste Italiane、Aruba、InfoCert など) または内務省の CIE IdP。

エンティティ構成とトラストチェーン

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

PKCE を使用した認証コード フローの実装

SPID/CIE OIDC の推奨フローは次のとおりです。PKCE を使用した認証コード フロー (コード交換用の証明キー、RFC 7636)。 PKCE は、認証コードの傍受から保護します。モバイル アプリには必須であり、Web アプリには推奨されます。

ステップ 1: 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()}`);
}

ステップ 2: コールバックとトークン交換

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

SPID および CIE の保証レベル (LoA)

政府のアイデンティティの最も重要な側面の 1 つは、 保証レベル (LoA)、 これは、認証プロセスの信頼性を示します。 SPID は 3 つのレベルを定義します。

レベル ACR URI 方法 一般的な使用方法
SpidL1 https://www.spid.gov.it/SpidL1 ユーザー名 + パスワード 低リスクのサービス、非機密データに関するコンサルティング
SpidL2 https://www.spid.gov.it/SpidL2 ユーザー名 + パスワード + OTP/プッシュ PAの標準サービス(申告・申請・支払い)
SpidL3 https://www.spid.gov.it/SpidL3 認定された物理デバイス 高リスクの操作、適格な電子署名

のために CIE (電子 ID カード)、保証レベルは常に LoA3 (最大) になります。 文書とPINを物理的に所有する必要があるため、CIE NFCチップを使用しました。これにより、CIE が当然の選択となります 強力な認証を必要とするサービス向け。

UserInfo エンドポイントと SPID/CIE 属性

ログイン後、証明書利用者はアクセス トークンを使用して UserInfo エンドポイントからユーザーの属性を取得できます。 SPID OIDC で使用できる属性は、技術プロファイルで定義されています。

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

プライバシーと SPID 属性の最小化

実際の必要性なしにユーザーの税コードを要求することは、GDPR の最小化原則に違反します。 AgID ガイドラインでは、リクエストされた各属性は、サービスの特定の機能によって動機付けられる必要があると指定されています。 プライバシーポリシーに記載されています。 AgID 監査中に、要求された属性を正当化できないと、 認定の取り消しにつながります。

SPID/CIE OIDC の SDK および公式ライブラリ

プロジェクト 開発者イタリア (developers.italia.it) はさまざまな言語で公式 SDK を提供します 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 ベースの認証応答モード

SPID/CIE OIDC ガイドラインでは、次の使用を推奨しています。 JARM (JWT 認可応答モード) 守るために 認可応答。合格する代わりに code e state クエリパラメータとしてクリアテキストで、 JARM は、アイデンティティ プロバイダーによって署名された JWT 内の応答をカプセル化します。

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

セッション管理とトークンリフレッシュ

政府のアイデンティティにおける重要な側面は、セッションを正しく管理することです。民間企業とは異なり、 PA サービスは、セッション期間とログアウトに関する厳しい制約を遵守する必要があります。

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

AgID の認定と準拠

SPID をそのサービスに統合するには、証明書利用者は AgID 認定プロセスに合格する必要があります。 OIDC の簡素化された手順は 2023 年から利用可能になります。

  1. 技術登録: Entity Configuration の構成とエンドポイントの検証 /.well-known/openid-federation。 AgID は自動ツールを通じてコン​​プライアンスを検証します。
  2. 機能テスト: spid-sp-test (オープンソース Python ツール) を使用して、正しい実装を検証します。 このツールは ID プロバイダーの動作をシミュレートし、200 を超えるテスト ケースを検証します。
  3. GDPRへの準拠: 必要な各 SPID 属性のデータ処理のドキュメント。
  4. 契約書の署名: SPID 技術規則およびメンバーシップ契約への同意。
## 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

コンバージェンスに向けて: SPID/CIE OIDC と EUDI ウォレット

SAML から OIDC への移行は最終目的地ではありません。エコシステムは完全な統合に向かって進んでいます と EUDIウォレット OpenID4VP を通じて。プロジェクト iam-プロキシ-イタリア GitHub ですでに実証済み IAM プロキシが SAML 2.0、OIDC、および OpenID4VC (IT ウォレット/EUDI ウォレット用) を同時にサポートできる方法。

最新の PA サービスの理想的なアーキテクチャでは、次の 3 つのフローすべてを透過的にサポートする必要があります。

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

ベストプラクティスとアンチパターン

SPID/CIE OIDC のベスト プラクティス

  • Web アプリケーションでも常に PKCE を使用します (ネイティブ アプリケーションでは必須)
  • JARM を実装して認証応答を保護する
  • RP 署名キーを 12 か月ごと (またはそれ以上の頻度で) ローテーションします。
  • アメリカ合衆国 sub 内部識別子としてペアごとに使用します。CF や電子メールなどの属性は使用しません
  • シングル ログアウト (SLO) の実装 — SPID 認定に必須
  • トラスト チェーンを最大 24 時間キャッシュします (その後更新します)。
  • エンティティ ステートメントの有効期限を監視します (通常は 24 時間)

避けるべきアンチパターン

  • ID トークンを保存しないでください。 localStorage または非 HttpOnly Cookie
  • 使用しないでください response_mode=query (アメリカ合衆国 form_post o form_post.jwt)
  • 正当な理由なく、アクセス トークンをフロントエンドに公開しないでください。
  • 不必要な SPID 属性を要求しないでください (認定リスク)
  • の検証を無視しないでください nonce トークンID内
  • 有効期限のないセッションを使用しないでください: PA セッションにはタイムアウトが必要です

結論

OpenID Connect は、イタリアとヨーロッパの政府認証の未来を表します。 SAML から OIDC への移行 それは単なる技術的なものではなく、より現代的なエコシステム、より優れたツール、そして自然な道をもたらします。 EUDIウォレットによる自己主権的アイデンティティに向けて。

開発者は今日から正しいパターン (PKCE、JARM、自動フェデレーション、 ペアごとのサブジェクト - OpenID4VP および EUDI ウォレットとのその後の統合に最適な位置にあります。 今日うまく書かれたコードは、明日書き直す必要はありません。それは公共デジタル サービスの継続性への投資です。

GovTechシリーズは続く

次の記事では、オープン データ API の設計と、パブリック データを効率的に公開および利用する方法について説明します。