---
url: 'https://www.corbado.com/it/blog/come-creare-verifiable-credential-issuer'
title: 'Come creare un Issuer di credenziali digitali (Guida per sviluppatori)'
description: 'Scopri come creare un issuer di Credenziali Verificabili W3C utilizzando il protocollo OpenID4VCI. Questa guida passo-passo mostra come creare un''applicazione Next.js che emette credenziali firmate crittograficamente e compatibili con i wallet digitali.'
lang: 'it'
author: 'Amine'
date: '2025-08-20T15:39:33.275Z'
lastModified: '2026-03-27T07:06:21.855Z'
keywords: 'issuer credenziali digitali, tutorial issuer, creare issuer'
category: 'Digital Credentials'
---

# Come creare un Issuer di credenziali digitali (Guida per sviluppatori)

## 1. Introduzione

Le [credenziali digitali](https://www.corbado.com/it/blog/digital-credentials-api) sono un modo efficace per
comprovare l'identità e le attestazioni in maniera sicura e rispettosa della privacy. Ma
come fanno gli utenti a ottenere queste credenziali? È qui che il ruolo dell'**Issuer**
diventa cruciale. Un [Issuer](https://www.corbado.com/glossary/issuer) è un'entità fidata, come un'agenzia
[governativa](https://www.corbado.com/passkeys-for-public-sector), un'università o una banca, responsabile della
creazione e della distribuzione di credenziali firmate digitalmente agli utenti.

Questa guida offre un tutorial completo e passo-passo per la creazione di un
[Issuer](https://www.corbado.com/glossary/issuer) di [credenziali digitali](https://www.corbado.com/it/blog/digital-credentials-api). Ci
concentreremo sul protocollo **OpenID for Verifiable Credential Issuance (OpenID4VCI)**,
uno standard moderno che definisce come gli utenti possono ottenere credenziali da un
[Issuer](https://www.corbado.com/glossary/issuer) e conservarle in modo sicuro nei loro
[wallet](https://www.corbado.com/blog/digital-wallet-assurance) digitali.

Il risultato finale sarà un'applicazione [Next.js](https://www.corbado.com/blog/nextjs-passkeys) funzionante in
grado di:

1. Accettare i dati dell'utente tramite un semplice modulo web.
2. Generare un'offerta di credenziale sicura e monouso.
3. Mostrare l'offerta come [codice QR](https://www.corbado.com/it/blog/codice-qr-login-autenticazione) che
   l'utente può scansionare con il suo [wallet](https://www.corbado.com/blog/digital-wallet-assurance) mobile.
4. Emettere una credenziale firmata crittograficamente che l'utente può conservare e
   presentare per la verifica.

### 1.1 Capire la terminologia: credenziali digitali e credenziali verificabili

Prima di procedere, è importante chiarire la distinzione tra due concetti correlati ma
diversi:

- **Credenziali digitali (termine generico):** si tratta di una categoria ampia che
  comprende qualsiasi forma digitale di credenziali, certificati o attestazioni. Possono
  includere semplici certificati digitali, [digital badge](https://www.corbado.com/glossary/digital-badge) di
  base o qualsiasi credenziale archiviata elettronicamente che può avere o meno funzioni
  di [sicurezza](https://www.corbado.com/it/blog/come-abilitare-passkey-su-android) crittografica.

- **Credenziali verificabili (VC - Standard W3C):** si tratta di un tipo specifico di
  credenziale digitale che segue lo standard W3C
  [Verifiable Credentials](https://www.corbado.com/glossary/microcredentials) Data Model. Le credenziali
  verificabili sono credenziali firmate crittograficamente, a prova di manomissione e
  rispettose della privacy che possono essere verificate in modo indipendente. Includono
  requisiti tecnici specifici come:
    - Firme crittografiche per l'autenticità e l'integrità
    - Modello di dati e formati standardizzati
    - Meccanismi di presentazione che tutelano la privacy
    - Protocolli di verifica interoperabili

**In questa guida, stiamo creando specificamente un issuer di credenziali verificabili**
che segue lo standard W3C, non un qualsiasi sistema di
[credenziali digitali](https://www.corbado.com/it/blog/digital-credentials-api). Il protocollo
[OpenID4VCI](https://www.corbado.com/glossary/openid4vci) che stiamo usando è progettato specificamente per
l'emissione di [credenziali verificabili](https://www.corbado.com/it/glossary/open-id-4-vp) e il formato JWT-VC
che implementeremo è un formato conforme a W3C per le
[credenziali verificabili](https://www.corbado.com/it/glossary/open-id-4-vp).

### 1.2 Come funziona

La magia dietro le credenziali digitali risiede in un modello semplice ma potente a
**"triangolo della fiducia"** che coinvolge tre attori chiave:

- **Issuer:** un'autorità fidata (ad esempio un'agenzia
  [governativa](https://www.corbado.com/passkeys-for-public-sector), un'università o una banca) che firma
  crittograficamente ed emette una credenziale a un utente. **Questo è il ruolo che stiamo
  creando in questa guida.**
- **Holder:** l'utente, che riceve la credenziale e la conserva in modo sicuro in un
  [wallet](https://www.corbado.com/blog/digital-wallet-assurance) digitale personale sul suo dispositivo.
- **Verifier:** un'applicazione o un servizio che deve controllare la credenziale
  dell'utente.

![W3C Verifiable Credentials Ecosystem](https://www.w3.org/TR/vc-data-model/diagrams/ecosystem.svg)

Il flusso di emissione è il primo passo in questo ecosistema. L'Issuer convalida le
informazioni dell'utente e gli fornisce una credenziale. Una volta che l'Holder ha questa
credenziale nel suo wallet, può presentarla a un Verifier per dimostrare la sua identità o
le sue attestazioni, completando il triangolo.

Ecco una rapida panoramica dell'applicazione finale in azione:

**Passaggio 1: inserimento dei dati dell'utente** L'utente compila un modulo con le sue
informazioni personali per richiedere una nuova credenziale.
![User Data Input Form](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/issuer_step_1_0733a9e1da.png)

**Passaggio 2: generazione dell'offerta di credenziali** L'applicazione genera un'offerta
di credenziali sicura, visualizzata come
[codice QR](https://www.corbado.com/it/blog/codice-qr-login-autenticazione) e un codice pre-autorizzato.
![Credential Offer QR Code](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/issuer_step_2_3f1881c473.png)

**Passaggio 3: interazione con il wallet** L'utente scansiona il
[codice QR](https://www.corbado.com/it/blog/codice-qr-login-autenticazione) con un wallet compatibile (ad
esempio, Sphereon Wallet) e inserisce un PIN per autorizzare l'emissione.
![Credential Offer on wallet](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/issuer_step_3_b80d689dfe.png)
![PIN Code insertion](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/issuer_step_4_ca8bad8d11.png)

**Passaggio 4: emissione della credenziale** Il wallet riceve e archivia la credenziale
digitale appena emessa, pronta per un uso futuro.
![Confirming the credential details](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/issuer_step_5_55b8150597.png)
![Credential added](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/issuer_step_6_7f5ac5745d.png)

## 2. Prerequisiti per creare un Issuer

Prima di immergerci nel codice, esaminiamo le conoscenze e gli strumenti fondamentali di
cui avrai bisogno. Questa guida presuppone una familiarità di base con i concetti di
sviluppo web, ma i seguenti prerequisiti sono essenziali per creare un issuer di
credenziali.

### 2.1 Scelta dei protocolli

Il nostro Issuer si basa su una serie di standard aperti che garantiscono
l'interoperabilità tra wallet e servizi di emissione. Per questo tutorial, ci
concentreremo sui seguenti:

| Standard / Protocollo                                             | Descrizione                                                                                                                                                                                                                                               |
| :---------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **OpenID4VCI**                                                    | **OpenID for Verifiable Credential Issuance.** È il protocollo principale che useremo. Definisce un flusso standard per il modo in cui un utente (tramite il suo wallet) può richiedere e ricevere una credenziale da un Issuer.                          |
| **[JWT-VC](https://www.w3.org/TR/vc-data-model/#json-web-token)** | **Credenziali verificabili basate su JWT.** Il formato per la credenziale che emetteremo. È uno standard W3C che codifica le credenziali verificabili come JSON Web Token (JWT), rendendole compatte e adatte al web.                                     |
| **[ISO mDoc](https://www.corbado.com/it/blog/digital-credentials-api)**                  | **ISO/IEC 18013-5.** Lo standard internazionale per le patenti di guida mobili (mDL). Sebbene emettiamo una JWT-VC, i _claims_ al suo interno sono strutturati per essere compatibili con il modello di dati mDoc (ad esempio `eu.europa.ec.eudi.pid.1`). |
| **OAuth 2.0**                                                     | Il framework di autorizzazione sottostante utilizzato da OpenID4VCI. Implementeremo un flusso `pre-authorized_code`, che è un tipo di grant specifico progettato per un'emissione di credenziali sicura e di facile utilizzo.                             |

#### 2.1.1 Flussi di autorizzazione: Pre-Authorized e Authorization Code

[OpenID4VCI](https://www.corbado.com/glossary/openid4vci) supporta due flussi di autorizzazione principali per
l'emissione di credenziali:

1. **Flusso Pre-Authorized Code:** in questo flusso, l'Issuer genera un codice monouso di
   breve durata (`pre-authorized_code`) che è immediatamente disponibile per l'utente. Il
   wallet dell'utente può quindi scambiare questo codice direttamente con una credenziale.
   Questo flusso è ideale per gli scenari in cui l'utente è già autenticato e presente sul
   sito web dell'Issuer, poiché fornisce un'esperienza di emissione istantanea e senza
   interruzioni, senza reindirizzamenti.

2. **Flusso Authorization Code:** è il flusso standard di [OAuth 2.0](https://www.corbado.com/glossary/oauth2),
   in cui l'utente viene reindirizzato a un server di autorizzazione per concedere il
   consenso. Dopo l'approvazione, il server invia un `authorization_code` a un
   `redirect_uri` registrato. Questo flusso è più adatto per le applicazioni di terze
   parti che avviano il processo di emissione per conto dell'utente.

**Per questo tutorial, useremo il flusso `pre-authorized_code`.** Abbiamo scelto questo
approccio perché è più semplice e offre un'esperienza utente più diretta per il nostro
caso d'uso specifico: un utente che richiede direttamente una credenziale dal sito web
dell'Issuer stesso. Elimina la necessità di complessi reindirizzamenti e registrazioni del
client, rendendo la logica di emissione di base più facile da capire e implementare.

Questa combinazione di standard ci permette di creare un issuer compatibile con una vasta
gamma di wallet digitali e garantisce un processo sicuro e standardizzato per l'utente.

### 2.2 Scelta dello stack tecnologico

Per creare il nostro issuer, useremo lo stesso stack tecnologico solido e moderno che
abbiamo usato per il verifier, garantendo un'esperienza di sviluppo coerente e di alta
qualità.

#### 2.2.1 Linguaggio: TypeScript

Useremo **TypeScript** sia per il nostro codice frontend che per quello backend. La sua
tipizzazione statica è preziosa in un'applicazione critica per la
[sicurezza](https://www.corbado.com/it/blog/come-abilitare-passkey-su-android) come un issuer, poiché aiuta a
prevenire errori comuni e migliora la qualità e la manutenibilità complessiva del codice.

#### 2.2.2 Framework: Next.js

**Next.js** è il nostro framework preferito perché offre un'esperienza integrata e senza
interruzioni per la creazione di applicazioni full-stack.

- **Per il frontend:** useremo [Next.js](https://www.corbado.com/blog/nextjs-passkeys) con
  [React](https://www.corbado.com/blog/react-passkeys) per creare l'interfaccia utente in cui gli utenti possono
  inserire i loro dati per richiedere una credenziale.
- **Per il backend:** sfrutteremo le **API Routes di Next.js** per creare gli endpoint
  lato server che gestiscono il flusso [OpenID4VCI](https://www.corbado.com/glossary/openid4vci), dalla
  generazione delle offerte di credenziali all'emissione della credenziale firmata finale.

#### 2.2.3 Librerie principali

La nostra implementazione si baserà su alcune librerie chiave per gestire attività
specifiche:

- **next**, **react** e **react-dom**: le librerie principali per la nostra applicazione
  [Next.js](https://www.corbado.com/blog/nextjs-passkeys).
- **mysql2**: un client [MySQL](https://www.corbado.com/blog/passkey-webauthn-database-guide) per
  [Node.js](https://www.corbado.com/blog/nodejs-passkeys), usato per archiviare i codici di autorizzazione e i
  dati di sessione.
- **uuid**: una libreria per generare identificatori unici, che useremo per creare i
  valori `pre-authorized_code`.
- **jose**: una solida libreria per la gestione delle JSON Web Signatures (JWS), che
  useremo per firmare crittograficamente le credenziali che emettiamo.

### 2.3 Ottenere un wallet di prova

Per testare il tuo issuer, avrai bisogno di un wallet mobile che supporti il protocollo
OpenID4VCI. Per questo tutorial, consigliamo lo **Sphereon Wallet**, disponibile sia per
[Android](https://www.corbado.com/blog/how-to-enable-passkeys-android) che per
[iOS](https://www.corbado.com/blog/how-to-enable-passkeys-ios).

**Come installare Sphereon Wallet:**

1. **Scarica il wallet** dal
   [Google Play Store](https://play.google.com/store/apps/details?id=com.sphereon.ssi.wallet)
   o dall'[Apple App Store](https://apps.apple.com/us/app/sphereon-wallet/id1661096796).
2. Installa l'app sul tuo dispositivo mobile.
3. Una volta installato, il wallet è pronto per ricevere offerte di credenziali
   scansionando un codice QR.

### 2.4 Conoscenze di crittografia

L'emissione di una credenziale è un'operazione critica per la
[sicurezza](https://www.corbado.com/it/blog/come-abilitare-passkey-su-android) che si basa su concetti
crittografici fondamentali per garantire fiducia e autenticità.

#### 2.4.1 Firme digitali

Nella sua essenza, una credenziale verificabile è un insieme di attestazioni che sono
state **firmate digitalmente** dall'Issuer. Questa firma fornisce due garanzie:

- **Autenticità:** dimostra che la credenziale è stata creata da un Issuer legittimo.
- **Integrità:** dimostra che la credenziale non è stata manomessa da quando è stata
  emessa.

#### 2.4.2 Crittografia a chiave pubblica/privata

Le firme digitali vengono create utilizzando la crittografia a chiave pubblica/privata.
Ecco come funziona:

1. **L'Issuer ha una coppia di chiavi:** una **chiave privata**, che viene mantenuta
   segreta e sicura, e una **chiave pubblica** corrispondente, che viene resa
   pubblicamente disponibile.
2. **Firma:** quando l'Issuer crea una credenziale, usa la sua **chiave privata** per
   generare una firma digitale unica per i dati della credenziale.
3. **Verifica:** un Verifier può successivamente usare la **chiave pubblica** dell'Issuer
   per controllare la firma. Se il controllo ha esito positivo, il Verifier sa che la
   credenziale è autentica e non è stata alterata.

Nella nostra implementazione, genereremo una coppia di chiavi Elliptic Curve (EC) e
useremo l'algoritmo `ES256` per firmare la JWT-VC. La chiave pubblica è incorporata nel
DID dell'Issuer (`did:web`), consentendo a qualsiasi Verifier di scoprirla e convalidare
la firma della credenziale.\
**Nota:** il claim `aud` (audience) è intenzionalmente omesso nei nostri JWT, poiché la
credenziale è progettata per essere di uso generale e non legata a un wallet specifico.\
Se vuoi limitare l'uso a un pubblico particolare, includi un claim `aud` e impostalo di
conseguenza.

## 3. Panoramica dell'architettura

La nostra applicazione Issuer è costruita come un progetto Next.js full-stack, con una
chiara separazione tra la logica del frontend e quella del backend. Questa architettura ci
permette di creare un'esperienza utente fluida gestendo al contempo tutte le operazioni
critiche per la sicurezza sul server.\
**Importante:** le tabelle `verification_sessions` e `verified_credentials` incluse
nell'SQL non sono necessarie per questo issuer, ma sono incluse per completezza.

- **Frontend (`src/app/issue/page.tsx`):** una singola pagina
  [React](https://www.corbado.com/blog/react-passkeys) che permette agli utenti di inserire i loro dati per
  richiedere una credenziale. Effettua chiamate API al nostro backend per avviare il
  processo di emissione.
- **Backend API Routes (`src/app/api/issue/...`):** un insieme di endpoint lato server che
  implementano il protocollo OpenID4VCI.
    - `/.well-known/openid-credential-issuer`: un endpoint pubblico di metadati. Questo è
      il primo URL che un wallet controllerà per scoprire le capacità dell'issuer, inclusi
      il suo server di autorizzazione, l'endpoint del token, l'endpoint delle credenziali
      e i tipi di credenziali che offre.
    - `/.well-known/openid-configuration`: un endpoint di discovery standard di OpenID
      Connect. Sebbene strettamente correlato a quello precedente, questo endpoint serve
      una configurazione più ampia legata a OIDC ed è spesso richiesto per
      l'interoperabilità con i client OpenID standard.
    - `/.well-known/did.json`: il DID Document per il nostro issuer. Quando si usa il
      metodo `did:web`, questo file viene usato per pubblicare le chiavi pubbliche
      dell'issuer, che i verifier possono usare per convalidare le firme delle credenziali
      che emette.
    - `authorize/route.ts`: crea un `pre-authorized_code` e un'offerta di credenziali.
    - `token/route.ts`: scambia il `pre-authorized_code` con un
      [access token](https://www.corbado.com/glossary/access-token).
    - `credential/route.ts`: emette la JWT-VC finale, firmata crittograficamente.
    - `schemas/pid/route.ts`: espone lo schema JSON per la credenziale PID. Ciò consente a
      qualsiasi consumatore della credenziale di comprenderne la struttura e i tipi di
      dati.
- **Libreria (`src/lib/`):**
    - `database.ts`: gestisce tutte le interazioni con il database, come l'archiviazione
      dei codici di autorizzazione e delle chiavi dell'issuer.
    - `crypto.ts`: gestisce tutte le operazioni crittografiche, inclusa la generazione di
      chiavi e la firma di JWT.

Ecco un diagramma che illustra il flusso di emissione:

![Digital Credential Issuance Flow](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/Mermaid_Chart_Create_complex_visual_diagrams_with_text_A_smarter_way_of_creating_diagrams_2025_07_29_145228_d28fd13731.svg)

## 4. Creazione dell'Issuer

Ora che abbiamo una solida comprensione degli standard, dei protocolli e
dell'architettura, possiamo iniziare a creare il nostro issuer.

> **Segui i passaggi o usa il codice finale**
>
> Ora esamineremo passo dopo passo la configurazione e l'implementazione del codice. Se
> preferisci passare direttamente al prodotto finito, puoi clonare il progetto completo
> dal nostro repository GitHub ed eseguirlo localmente.
>
> ```bash
> git clone https://github.com/corbado/digital-credentials-example.git
> ```

### 4.1 Impostazione del progetto

Per prima cosa, inizializzeremo un nuovo progetto Next.js, installeremo le dipendenze
necessarie e avvieremo il nostro database.

#### 4.1.1 Inizializzazione dell'app Next.js

Apri il tuo terminale, vai alla directory in cui vuoi creare il tuo progetto ed esegui il
seguente comando. Per questo progetto stiamo usando l'App Router, TypeScript e Tailwind
CSS.

```bash
npx create-next-app@latest . --ts --eslint --tailwind --app --src-dir --import-alias "@/*" --use-npm
```

Questo comando crea lo scaffolding di una nuova applicazione Next.js nella tua directory
corrente.

#### 4.1.2 Installazione delle dipendenze

Successivamente, dobbiamo installare le librerie che gestiranno i JWT, le connessioni al
database e la generazione di UUID.

```bash
npm install jose mysql2 uuid @types/uuid
```

Questo comando installa:

- `jose`: per firmare e verificare i JSON Web Token (JWT).
- `mysql2`: il client [MySQL](https://www.corbado.com/blog/passkey-webauthn-database-guide) per il nostro
  database.
- `uuid`: per generare stringhe di challenge uniche.
- `@types/uuid`: i tipi TypeScript per la libreria `uuid`.

#### 4.1.3 Avvio del database

Il nostro backend richiede un database [MySQL](https://www.corbado.com/blog/passkey-webauthn-database-guide) per
archiviare i codici di autorizzazione, le sessioni di emissione e le chiavi dell'issuer.
Abbiamo incluso un file `docker-compose.yml` per semplificare questa operazione.

Se hai clonato il repository, puoi semplicemente eseguire `docker-compose up -d`. Se stai
costruendo da zero, crea un file chiamato `docker-compose.yml` con il seguente contenuto:

```yaml
services:
    mysql:
        image: mysql:8.0
        restart: always
        environment:
            MYSQL_ROOT_PASSWORD: rootpassword
            MYSQL_DATABASE: digital_credentials
            MYSQL_USER: app_user
            MYSQL_PASSWORD: app_password
        ports:
            - "3306:3306"
        volumes:
            - mysql_data:/var/lib/mysql
            - ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql
        healthcheck:
            test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
            timeout: 20s
            retries: 10

volumes:
    mysql_data:
```

Questa configurazione di Docker Compose richiede anche uno script di inizializzazione SQL.
Crea una directory chiamata `sql` e al suo interno un file chiamato `init.sql` con il
seguente contenuto per configurare le tabelle necessarie sia per il verifier che per
l'issuer:

```sql
-- Create database if not exists
CREATE DATABASE IF NOT EXISTS digital_credentials;
USE digital_credentials;

-- Table for storing challenges
CREATE TABLE IF NOT EXISTS challenges (
    id VARCHAR(36) PRIMARY KEY,
    challenge VARCHAR(255) NOT NULL UNIQUE,
    expires_at TIMESTAMP NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    used BOOLEAN DEFAULT FALSE,
    INDEX idx_challenge (challenge),
    INDEX idx_expires_at (expires_at)
);

-- Table for storing verification sessions
CREATE TABLE IF NOT EXISTS verification_sessions (
    id VARCHAR(36) PRIMARY KEY,
    challenge_id VARCHAR(36),
    status ENUM('pending', 'verified', 'failed', 'expired') DEFAULT 'pending',
    presentation_data JSON,
    verified_at TIMESTAMP NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (challenge_id) REFERENCES challenges(id) ON DELETE CASCADE,
    INDEX idx_challenge_id (challenge_id),
    INDEX idx_status (status)
);

-- Table for storing verified credentials data (optional)
CREATE TABLE IF NOT EXISTS verified_credentials (
    id VARCHAR(36) PRIMARY KEY,
    session_id VARCHAR(36),
    credential_type VARCHAR(255),
    issuer VARCHAR(255),
    subject VARCHAR(255),
    claims JSON,
    verified_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (session_id) REFERENCES verification_sessions(id) ON DELETE CASCADE,
    INDEX idx_session_id (session_id),
    INDEX idx_credential_type (credential_type)
);

-- ISSUER TABLES

-- Table for storing authorization codes in OpenID4VCI flow
CREATE TABLE IF NOT EXISTS authorization_codes (
    id VARCHAR(36) PRIMARY KEY,
    code VARCHAR(255) NOT NULL UNIQUE,
    client_id VARCHAR(255),
    scope VARCHAR(255),
    code_challenge VARCHAR(255),
    code_challenge_method VARCHAR(50),
    redirect_uri TEXT,
    user_pin VARCHAR(10),
    expires_at TIMESTAMP NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    used BOOLEAN DEFAULT FALSE,
    INDEX idx_code (code),
    INDEX idx_expires_at (expires_at)
);

-- Table for storing issuance sessions
CREATE TABLE IF NOT EXISTS issuance_sessions (
    id VARCHAR(36) PRIMARY KEY,
    authorization_code_id VARCHAR(36),
    access_token VARCHAR(255),
    token_type VARCHAR(50) DEFAULT 'Bearer',
    expires_in INT DEFAULT 3600,
    c_nonce VARCHAR(255),
    c_nonce_expires_at TIMESTAMP,
    status ENUM('pending', 'authorized', 'credential_issued', 'expired', 'failed') DEFAULT 'pending',
    user_data JSON,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (authorization_code_id) REFERENCES authorization_codes(id) ON DELETE CASCADE,
    INDEX idx_access_token (access_token),
    INDEX idx_c_nonce (c_nonce),
    INDEX idx_status (status)
);

-- Table for storing issued credentials
CREATE TABLE IF NOT EXISTS issued_credentials (
    id VARCHAR(36) PRIMARY KEY,
    session_id VARCHAR(36),
    credential_id VARCHAR(255),
    credential_type VARCHAR(255) DEFAULT 'jwt_vc',
    doctype VARCHAR(255) DEFAULT 'eu.europa.ec.eudi.pid.1',
    credential_data LONGTEXT, -- Base64 encoded mDoc
    credential_claims JSON,
    issuer_did VARCHAR(255),
    subject_id VARCHAR(255),
    issued_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    expires_at TIMESTAMP,
    revoked BOOLEAN DEFAULT FALSE,
    revoked_at TIMESTAMP NULL,
    FOREIGN KEY (session_id) REFERENCES issuance_sessions(id) ON DELETE CASCADE,
    INDEX idx_credential_id (credential_id),
    INDEX idx_session_id (session_id),
    INDEX idx_doctype (doctype),
    INDEX idx_subject_id (subject_id),
    INDEX idx_issued_at (issued_at)
);

-- Table for storing issuer keys (simplified for demo)
CREATE TABLE IF NOT EXISTS issuer_keys (
    id VARCHAR(36) PRIMARY KEY,
    key_id VARCHAR(255) NOT NULL UNIQUE,
    key_type VARCHAR(50) NOT NULL, -- 'EC', 'RSA'
    algorithm VARCHAR(50) NOT NULL, -- 'ES256', 'RS256', etc.
    public_key TEXT NOT NULL, -- JWK format
    private_key TEXT NOT NULL, -- JWK format (encrypted in production)
    is_active BOOLEAN DEFAULT TRUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_key_id (key_id),
    INDEX idx_is_active (is_active)
);
```

Una volta che entrambi i file sono a posto, apri il tuo terminale nella radice del
progetto ed esegui:

```bash
docker-compose up -d
```

Questo comando avvierà un container MySQL in background, pronto per essere usato dalla
nostra applicazione.

### 4.2 Implementazione delle librerie condivise

Prima di creare gli endpoint API, creiamo le librerie condivise che gestiranno la logica
di business principale. Questo approccio mantiene le nostre API routes pulite e
focalizzate sulla gestione delle richieste HTTP, mentre il lavoro complesso è delegato a
questi moduli.

#### 4.2.1 La libreria del database (`src/lib/database.ts`)

Questo file è l'unica fonte di verità per tutte le interazioni con il database. Usa la
libreria `mysql2` per connettersi al nostro container MySQL e fornisce un insieme di
funzioni esportate per creare, leggere e aggiornare i record nelle nostre tabelle. Questo
strato di astrazione rende il nostro codice più modulare e più facile da mantenere.

Crea il file `src/lib/database.ts` con il seguente contenuto:

```typescript
// src/lib/database.ts
import mysql from "mysql2/promise";

// Database connection configuration
const dbConfig = {
    host: process.env.DATABASE_HOST || "localhost",
    port: parseInt(process.env.DATABASE_PORT || "3306"),
    user: process.env.DATABASE_USER || "app_user",
    password: process.env.DATABASE_PASSWORD || "app_password",
    database: process.env.DATABASE_NAME || "digital_credentials",
    timezone: "+00:00",
};

let connection: mysql.Connection | null = null;

export async function getConnection(): Promise<mysql.Connection> {
    if (!connection) {
        connection = await mysql.createConnection(dbConfig);
    }
    return connection;
}

// Data-Access-Object (DAO) functions for each table
// ... (e.g., createChallenge, getChallenge, createAuthorizationCode, etc.)
```

> **Nota:** per brevità, l'elenco completo delle funzioni DAO è stato omesso. Puoi trovare
> il codice completo nel
> [repository del progetto](https://github.com/corbado/digital-credentials-example/blob/main/src/lib/database.ts).
> Questo file include funzioni per la gestione di challenge, sessioni di verifica, codici
> di autorizzazione, sessioni di emissione e chiavi dell'issuer.

#### 4.2.2 La libreria di crittografia (`src/lib/crypto.ts`)

Questo file gestisce tutte le operazioni crittografiche critiche per la sicurezza. Usa la
libreria `jose` per generare coppie di chiavi e firmare JSON Web Token (JWT).

**Generazione delle chiavi** La funzione `generateIssuerKeyPair` crea una nuova coppia di
chiavi Elliptic Curve che verrà usata per firmare le credenziali. La chiave pubblica è
esportata in formato JSON Web Key (JWK) in modo da poter essere pubblicata nel nostro
documento `did.json`.

```typescript
// src/lib/crypto.ts
import { generateKeyPair, exportJWK, SignJWT } from "jose";

export async function generateIssuerKeyPair(keyId: string, issuerDid: string) {
    const { publicKey, privateKey } = await generateKeyPair("ES256", {
        crv: "P-256",
        extractable: true,
    });

    const publicKeyJWK = await exportJWK(publicKey);
    publicKeyJWK.kid = keyId; // Assign a unique key ID

    // ... (private key export and other setup)

    return { publicKey, privateKey, publicKeyJWK /* ... */ };
}
```

**Creazione di credenziali JWT** La funzione `createJWTVerifiableCredential` è il cuore
del processo di emissione. Prende i claim dell'utente, la coppia di chiavi dell'issuer e
altri metadati e li usa per creare una JWT-VC firmata.

```typescript
// src/lib/crypto.ts

export async function createJWTVerifiableCredential(
    claims: MDocClaims,
    issuerKeyPair: IssuerKeyPair,
    subjectId: string,
    audience: string,
): Promise<string> {
    const now = Math.floor(Date.now() / 1000);
    const oneYear = 365 * 24 * 60 * 60;

    const vcPayload = {
        // The issuer's DID
        iss: issuerKeyPair.issuerDid,
        // The subject's (holder's) DID
        sub: subjectId,
        // The time the credential was issued (iat) and when it expires (exp)
        iat: now,
        exp: now + oneYear,
        // The Verifiable Credential data model
        vc: {
            "@context": [
                "https://www.w3.org/2018/credentials/v1",
                "https://europa.eu/eudi/pid/v1",
            ],
            type: ["VerifiableCredential", "eu.europa.ec.eudi.pid.1"],
            issuer: issuerKeyPair.issuerDid,
            issuanceDate: new Date(now * 1000).toISOString(),
            credentialSubject: {
                id: subjectId,
                ...claims,
            },
        },
    };

    // Sign the payload with the issuer's private key
    return await new SignJWT(vcPayload)
        .setProtectedHeader({
            alg: issuerKeyPair.algorithm,
            kid: issuerKeyPair.keyId,
            typ: "JWT",
        })
        .sign(issuerKeyPair.privateKey);
}
```

Questa funzione costruisce il payload JWT secondo il W3C
[Verifiable Credentials](https://www.corbado.com/glossary/microcredentials) Data Model e lo firma con la chiave
privata dell'issuer, producendo una credenziale verificabile sicura.

### 4.2 Panoramica dell'architettura dell'app Next.js

La nostra applicazione Next.js è strutturata per separare le responsabilità tra frontend e
backend, anche se fanno parte dello stesso progetto. Ciò si ottiene sfruttando l'App
Router sia per le pagine dell'interfaccia utente che per gli endpoint API.

- **Frontend (`src/app/issue/page.tsx`):** un singolo componente di pagina
  [React](https://www.corbado.com/blog/react-passkeys) che definisce l'interfaccia utente per la route `/issue`.
  Gestisce l'input dell'utente e comunica con la nostra API di backend.

- **Backend API Routes (`src/app/api/...`):**
    - **Discovery (`.well-known/.../route.ts`):** queste routes espongono endpoint di
      metadati pubblici che consentono ai wallet e ad altri client di scoprire le capacità
      e le chiavi pubbliche dell'issuer.
    - **Issuance (`issue/.../route.ts`):** questi endpoint implementano la logica
      principale di OpenID4VCI, inclusa la creazione di offerte di credenziali,
      l'emissione di token e la firma della credenziale finale.
    - **Schema (`schemas/pid/route.ts`):** questa route serve lo schema JSON per la
      credenziale, definendone la struttura.

- **Libreria (`src/lib/`):** questa directory contiene la logica riutilizzabile condivisa
  in tutto il backend.
    - `database.ts`: gestisce tutte le interazioni con il database, astraendo le query
      SQL.
    - `crypto.ts`: gestisce tutte le operazioni crittografiche, come la generazione di
      chiavi e la firma di JWT.

Questa chiara separazione rende l'applicazione modulare e più facile da mantenere.

**Nota:** la funzione `generateIssuerDid()` deve restituire un `did:web` valido che
corrisponda al dominio del tuo issuer.\
Quando viene distribuito, il file `.well-known/did.json` deve essere servito su HTTPS a
quel dominio affinché i verifier possano convalidare le credenziali.

![Architectural overview of Next.js App](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/Mermaid_Chart_Create_complex_visual_diagrams_with_text_A_smarter_way_of_creating_diagrams_2025_07_29_151549_6a0aca6477.svg)

### 4.3 Creazione del frontend

Il nostro frontend è una singola pagina React che fornisce un semplice modulo per
consentire agli utenti di richiedere una nuova credenziale digitale. Le sue responsabilità
sono:

- Acquisire i dati dell'utente (nome, data di nascita, ecc.).
- Inviare questi dati al nostro backend per creare un'offerta di credenziali.
- Visualizzare il codice QR e il PIN risultanti affinché l'utente possa scansionarli con
  il suo wallet.

La logica principale è gestita nella funzione `handleSubmit`, che viene attivata quando
l'utente invia il modulo.

```typescript
// src/app/issue/page.tsx

const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setLoading(true);
    setError(null);
    setCredentialOffer(null);

    try {
        // 1. Validate required fields
        if (!userData.given_name || !userData.family_name || !userData.birth_date) {
            throw new Error("Please fill in all required fields");
        }

        // 2. Request a credential offer from the backend
        const response = await fetch("/api/issue/authorize", {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
            },
            body: JSON.stringify({
                user_data: userData,
            }),
        });

        if (!response.ok) {
            const errorData = await response.json();
            throw new Error(
                errorData.error_description || "Failed to create credential offer",
            );
        }

        // 3. Set the credential offer in state to display the QR code
        const result = await response.json();
        setCredentialOffer(result);
    } catch (err) {
        const errorMessage = (err as Error).message || "Unknown error occurred";
        setError(errorMessage);
    } finally {
        setLoading(false);
    }
};
```

Questa funzione esegue tre azioni chiave:

1. **Convalida i dati del modulo** per garantire che tutti i campi obbligatori siano
   compilati.
2. **Invia una richiesta `POST`** al nostro endpoint `/api/issue/authorize` con i dati
   dell'utente.
3. **Aggiorna lo stato del componente** con l'offerta di credenziali ricevuta dal backend,
   che fa sì che l'interfaccia utente visualizzi il codice QR e il codice di transazione.

Il resto del file contiene codice React standard per il rendering del modulo e la
visualizzazione del codice QR. Puoi visualizzare il file completo nel
[repository del progetto](https://github.com/corbado/digital-credentials-example/blob/main/src/app/issue/page.tsx).

### 4.4 Impostazione dell'ambiente e del discovery

Prima di creare l'API di backend, dobbiamo configurare il nostro ambiente e impostare gli
endpoint di discovery. Questi file `.well-known` sono fondamentali affinché i wallet
possano trovare il nostro issuer e capire come interagire con esso.

#### 4.4.1 Creare il file di ambiente

Crea un file chiamato `.env.local` nella radice del tuo progetto e aggiungi la seguente
riga. Questo URL deve essere accessibile pubblicamente affinché un wallet mobile possa
raggiungerlo. Per lo sviluppo locale, puoi usare un servizio di tunneling come
[ngrok](https://www.corbado.com/blog/multi-device-passkey-login-corbado-ngrok) per esporre il tuo `localhost`.

```
NEXT_PUBLIC_BASE_URL=http://localhost:3000
```

#### 4.4.2 Implementare gli endpoint di discovery

I wallet scoprono le capacità di un issuer interrogando URL standard `.well-known`.
Dobbiamo creare tre di questi endpoint.

**1. Metadati dell'issuer (`/.well-known/openid-credential-issuer`)**

Questo è il file di discovery principale per OpenID4VCI. Dice al wallet tutto ciò che deve
sapere sull'issuer, inclusi i suoi endpoint, i tipi di credenziali che offre e gli
algoritmi crittografici supportati.

Crea il file `src/app/.well-known/openid-credential-issuer/route.ts`:

```typescript
// src/app/.well-known/openid-credential-issuer/route.ts
import { NextResponse } from "next/server";

export async function GET() {
    const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000";

    const issuerMetadata = {
        // The issuer's unique identifier.
        issuer: baseUrl,
        // The URL of the authorization server. For simplicity, our issuer is its own authorization server.
        authorization_servers: [baseUrl],
        // The URL of the credential issuer.
        credential_issuer: baseUrl,
        // The endpoint where the wallet will POST to receive the actual credential.
        credential_endpoint: `${baseUrl}/api/issue/credential`,
        // The endpoint where the wallet exchanges an authorization code for an access token.
        token_endpoint: `${baseUrl}/api/issue/token`,
        // The endpoint for the authorization flow (not used in our pre-authorized flow, but good practice to include).
        authorization_endpoint: `${baseUrl}/api/issue/authorize`,
        // Indicates support for the pre-authorized code flow without requiring client authentication.
        pre_authorized_grant_anonymous_access_supported: true,
        // Human-readable information about the issuer.
        display: [
            {
                name: "Corbado Credentials Issuer",
                locale: "en-US",
            },
        ],
        // A list of the credential types this issuer can issue.
        credential_configurations_supported: {
            "eu.europa.ec.eudi.pid.1": {
                // The format of the credential (e.g., jwt_vc, mso_mdoc).
                format: "jwt_vc",
                // The specific document type, conforming to ISO mDoc standards.
                doctype: "eu.europa.ec.eudi.pid.1",
                // The OAuth 2.0 scope associated with this credential type.
                scope: "eu.europa.ec.eudi.pid.1",
                // Methods the wallet can use to prove possession of its key.
                cryptographic_binding_methods_supported: ["jwk"],
                // Signing algorithms the issuer supports for this credential.
                credential_signing_alg_values_supported: ["ES256"],
                // Proof-of-possession types the wallet can use.
                proof_types_supported: {
                    jwt: {
                        proof_signing_alg_values_supported: ["ES256", "ES384", "ES512"],
                    },
                },
                // Display properties for the credential.
                display: [
                    {
                        name: "Corbado Credential Issuer",
                        locale: "en-US",
                        logo: {
                            uri: `${baseUrl}/logo.png`,
                            alt_text: "EU Digital Identity",
                        },
                        background_color: "#003399",
                        text_color: "#FFFFFF",
                    },
                ],
                // A list of the claims (attributes) in the credential.
                claims: {
                    "eu.europa.ec.eudi.pid.1": {
                        given_name: {
                            mandatory: true,
                            display: [{ name: "Given Name", locale: "en-US" }],
                        },
                        family_name: {
                            mandatory: true,
                            display: [{ name: "Family Name", locale: "en-US" }],
                        },
                        birth_date: {
                            mandatory: true,
                            display: [{ name: "Date of Birth", locale: "en-US" }],
                        },
                    },
                },
            },
        },
        // Authentication methods supported by the token endpoint. 'none' means public client.
        token_endpoint_auth_methods_supported: ["none"],
        // PKCE code challenge methods supported.
        code_challenge_methods_supported: ["S256"],
        // OAuth 2.0 grant types the issuer supports.
        grant_types_supported: [
            "authorization_code",
            "urn:ietf:params:oauth:grant-type:pre-authorized_code",
        ],
    };

    return NextResponse.json(issuerMetadata, {
        headers: {
            "Content-Type": "application/json",
            "Cache-Control": "no-cache, no-store, must-revalidate",
            Pragma: "no-cache",
            Expires: "0",
        },
    });
}
```

**2. Configurazione OpenID (`/.well-known/openid-configuration`)**

Questo è un documento di discovery OIDC standard che fornisce un insieme più ampio di
dettagli di configurazione.

Crea il file `src/app/.well-known/openid-configuration/route.ts`:

```typescript
// src/app/.well-known/openid-configuration/route.ts
import { NextResponse } from "next/server";

export async function GET() {
    const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000";

    const openidConfiguration = {
        // The issuer's unique identifier.
        credential_issuer: baseUrl,
        // The endpoint where the wallet will POST to receive the actual credential.
        credential_endpoint: `${baseUrl}/api/issue/credential`,
        // The endpoint for the authorization flow.
        authorization_endpoint: `${baseUrl}/api/issue/authorize`,
        // The endpoint where the wallet exchanges an authorization code for an access token.
        token_endpoint: `${baseUrl}/api/issue/token`,
        // A list of the credential types this issuer can issue.
        credential_configurations_supported: {
            "eu.europa.ec.eudi.pid.1": {
                format: "jwt_vc",
                scope: "eu.europa.ec.eudi.pid.1",
                cryptographic_binding_methods_supported: ["jwk"],
                credential_signing_alg_values_supported: ["ES256", "ES384", "ES512"],
                proof_types_supported: {
                    jwt: {
                        proof_signing_alg_values_supported: ["ES256", "ES384", "ES512"],
                    },
                },
            },
        },
        // OAuth 2.0 grant types the issuer supports.
        grant_types_supported: [
            "authorization_code",
            "urn:ietf:params:oauth:grant-type:pre-authorized_code",
        ],
        // Indicates support for the pre-authorized code flow.
        pre_authorized_grant_anonymous_access_supported: true,
        // PKCE code challenge methods supported.
        code_challenge_methods_supported: ["S256"],
        // Authentication methods supported by the token endpoint.
        token_endpoint_auth_methods_supported: ["none"],
        // OAuth 2.0 scopes the issuer supports.
        scopes_supported: ["eu.europa.ec.eudi.pid.1"],
    };

    return NextResponse.json(openidConfiguration, {
        headers: {
            "Content-Type": "application/json",
            "Cache-Control": "no-cache, no-store, must-revalidate",
            Pragma: "no-cache",
            Expires: "0",
        },
    });
}
```

**3. Documento DID (`/.well-known/did.json`)**

Questo file pubblica la chiave pubblica dell'issuer usando il metodo `did:web`,
consentendo a chiunque di verificare la firma delle credenziali emesse da esso.

Crea il file `src/app/.well-known/did.json/route.ts`:

```typescript
// src/app/.well-known/did.json/route.ts
import { NextResponse } from "next/server";
import { getActiveIssuerKey } from "../../../lib/database";
import { generateIssuerDid } from "../../../lib/crypto";

export async function GET() {
    const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000";
    const issuerKey = await getActiveIssuerKey();

    if (!issuerKey) {
        return NextResponse.json(
            { error: "No active issuer key found" },
            { status: 404 },
        );
    }

    const publicKeyJWK = JSON.parse(issuerKey.public_key);
    const didId = generateIssuerDid();
    const didDocument = {
        // The context defines the vocabulary used in the document.
        "@context": [
            "https://www.w3.org/ns/did/v1",
            "https://w3id.org/security/suites/jws-2020/v1",
        ],
        // The DID URI, which is the unique identifier for the issuer.
        id: didId,
        // The DID controller, which is the entity that controls the DID. Here, it's the issuer itself.
        controller: didId,
        // A list of public keys that can be used to verify signatures from the issuer.
        verificationMethod: [
            {
                // A unique identifier for the key, scoped to the DID.
                id: `${didId}#${issuerKey.key_id}`,
                // The type of the key.
                type: "JsonWebKey2020",
                // The DID of the key's controller.
                controller: didId,
                // The public key in JWK format.
                publicKeyJwk: publicKeyJWK,
            },
        ],
        // Specifies which keys can be used for authentication (proving control of the DID).
        authentication: [`${didId}#${issuerKey.key_id}`],
        // Specifies which keys can be used for creating verifiable credentials.
        assertionMethod: [`${didId}#${issuerKey.key_id}`],
        // A list of services provided by the DID subject, such as the issuer endpoint.
        service: [
            {
                id: `${didId}#openid-credential-issuer`,
                type: "OpenIDCredentialIssuer",
                serviceEndpoint: `${baseUrl}/.well-known/openid-credential-issuer`,
            },
        ],
    };

    return NextResponse.json(didDocument, {
        headers: {
            "Content-Type": "application/did+json",
            "Cache-Control": "no-cache, no-store, must-revalidate",
            Pragma: "no-cache",
            Expires: "0",
        },
    });
}
```

> **Perché non usare la cache?** Noterai che tutti e tre questi endpoint restituiscono
> intestazioni che impediscono aggressivamente la memorizzazione nella cache
> (`Cache-Control: no-cache`, `Pragma: no-cache`, `Expires: 0`). Questa è una pratica di
> sicurezza fondamentale per i documenti di discovery. Le configurazioni dell'issuer
> possono cambiare, ad esempio, una chiave crittografica potrebbe essere ruotata. Se un
> wallet o un client memorizzasse nella cache una vecchia versione del file `did.json` o
> `openid-credential-issuer`, non riuscirebbe a convalidare nuove credenziali o a
> interagire con endpoint aggiornati. Forzando i client a recuperare una copia aggiornata
> a ogni richiesta, ci assicuriamo che abbiano sempre le informazioni più recenti.

#### 4.4.3 Implementare l'endpoint dello schema delle credenziali

L'ultimo pezzo della nostra [infrastruttura](https://www.corbado.com/passkeys-for-critical-infrastructure)
pubblica è l'endpoint dello schema delle credenziali. Questa route serve uno schema JSON
che definisce formalmente la struttura, i tipi di dati e i vincoli della credenziale PID
che stiamo emettendo. Wallet e verifier possono usare questo schema per convalidare il
contenuto della credenziale.

Crea il file `src/app/api/schemas/pid/route.ts` con il seguente contenuto:

```typescript
// src/app/api/schemas/pid/route.ts
import { NextResponse } from "next/server";

export async function GET() {
    const schema = {
        $schema: "https://json-schema.org/draft/2020-12/schema",
        $id: "https://example.com/schemas/pid", // Replace with your actual domain
        title: "PID Credential",
        description:
            "A schema for a Verifiable Credential representing a Personal Identification Document (PID).",
        type: "object",
        properties: {
            credentialSubject: {
                type: "object",
                properties: {
                    given_name: { type: "string" },
                    family_name: { type: "string" },
                    birth_date: { type: "string", format: "date" },
                    // ... other properties of the credential subject
                },
                required: ["given_name", "family_name", "birth_date"],
            },
            // ... other top-level properties of a Verifiable Credential
        },
    };

    return NextResponse.json(schema, {
        headers: {
            "Content-Type": "application/schema+json",
            "Access-Control-Allow-Origin": "*", // Allow cross-origin requests
        },
    });
}
```

> **Nota:** lo schema JSON per una credenziale PID può essere piuttosto grande e
> dettagliato. Per brevità, lo schema completo è stato troncato. Puoi trovare il file
> completo nel
> [repository del progetto](https://github.com/corbado/digital-credentials-example/blob/main/src/app/api/schemas/pid/route.ts).

### 4.5 Creazione degli endpoint di backend

Con il frontend a posto, ora abbiamo bisogno della logica lato server per gestire il
flusso OpenID4VCI. Inizieremo con il primo endpoint che il frontend chiama:
`/api/issue/authorize`.

#### 4.5.1 `/api/issue/authorize`: creare l'offerta di credenziali

Questo endpoint è responsabile di prendere i dati dell'utente, generare un codice monouso
sicuro e costruire un'`credential_offer` che il wallet dell'utente possa capire.

Ecco la logica principale:

```typescript
// src/app/api/issue/authorize/route.ts
import { NextRequest, NextResponse } from "next/server";
import { v4 as uuidv4 } from "uuid";
import { createAuthorizationCode } from "@/lib/database";

export async function POST(request: NextRequest) {
    try {
        const body = await request.json();
        const { user_data } = body;

        // 1. Validate user data
        if (
            !user_data ||
            !user_data.given_name ||
            !user_data.family_name ||
            !user_data.birth_date
        ) {
            return NextResponse.json({ error: "missing_user_data" }, { status: 400 });
        }

        // 2. Generate a pre-authorized code and a PIN
        const code = uuidv4();
        const expiresAt = new Date(Date.now() + 10 * 60 * 1000); // 10 minutes
        const txCode = Math.floor(1000 + Math.random() * 9000).toString(); // 4-digit PIN

        // 3. Store the code and user data
        await createAuthorizationCode(uuidv4(), code, expiresAt);
        // Note: This uses an in-memory store for demo purposes only.
        // In production, persist data securely in a database with proper expiry.
        if (!(global as any).userDataStore) (global as any).userDataStore = new Map();
        (global as any).userDataStore.set(code, user_data);
        if (!(global as any).txCodeStore) (global as any).txCodeStore = new Map();
        (global as any).txCodeStore.set(code, txCode);

        // 4. Create the credential offer object
        const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000";
        const credentialOffer = {
            // The issuer's identifier, which is its base URL.
            credential_issuer: baseUrl,
            // An array of credential types the issuer is offering.
            credential_configuration_ids: ["eu.europa.ec.eudi.pid.1"],
            // Specifies the grant types the wallet can use.
            grants: {
                // We are using the pre-authorized code flow.
                "urn:ietf:params:oauth:grant-type:pre-authorized_code": {
                    // The one-time code the wallet will exchange for a token.
                    "pre-authorized_code": code,
                    // Indicates that the user must enter a PIN (tx_code) to redeem the code.
                    user_pin_required: true,
                },
            },
        };

        // 5. Create the full credential offer URI (a deep link for wallets)
        const credentialOfferUri = `openid-credential-offer://?credential_offer=${encodeURIComponent(
            JSON.stringify(credentialOffer),
        )}`;

        // The final response to the frontend.
        return NextResponse.json({
            // The deep link for the QR code.
            credential_offer_uri: credentialOfferUri,
            // The raw pre-authorized code, for display or manual entry.
            pre_authorized_code: code,
            // The 4-digit PIN the user must enter in their wallet.
            tx_code: txCode,
        });
    } catch (error) {
        console.error("Authorization error:", error);
        return NextResponse.json({ error: "server_error" }, { status: 500 });
    }
}
```

Passaggi chiave in questo endpoint:

1. **Convalida dei dati:** per prima cosa, si assicura che i dati utente richiesti siano
   presenti.
2. **Generazione di codici:** crea un `pre-authorized_code` univoco (un UUID) e un
   `tx_code` a 4 cifre (PIN) per un ulteriore livello di sicurezza.
3. **Persistenza dei dati:** il `pre-authorized_code` viene archiviato nel database con un
   breve tempo di scadenza. I dati dell'utente e il PIN vengono archiviati in memoria,
   collegati al codice.
4. **Costruzione dell'offerta:** costruisce l'oggetto `credential_offer` secondo le
   specifiche OpenID4VCI. Questo oggetto indica al wallet dove si trova l'issuer, quali
   credenziali offre e il codice necessario per ottenerle.
5. **Restituzione dell'URI:** infine, crea un URI di deep link
   (`openid-credential-offer://...`) e lo restituisce al frontend, insieme al `tx_code`
   affinché l'utente lo veda.

#### 4.5.2 `/api/issue/token`: scambiare il codice con un token

Una volta che l'utente scansiona il codice QR e inserisce il suo PIN, il wallet effettua
una richiesta `POST` a questo endpoint. Il suo compito è convalidare il
`pre-authorized_code` e lo `user_pin` (PIN) e, se sono validi, emettere un
[access token](https://www.corbado.com/glossary/access-token) di breve durata.

Crea il file `src/app/api/issue/token/route.ts` con il seguente contenuto:

```typescript
// src/app/api/issue/token/route.ts
import { NextRequest, NextResponse } from "next/server";
import { v4 as uuidv4 } from "uuid";
import {
    getAuthorizationCode,
    markAuthorizationCodeAsUsed,
    createIssuanceSession,
} from "@/lib/database";

export async function POST(request: NextRequest) {
    try {
        const formData = await request.formData();
        const grant_type = formData.get("grant_type") as string;
        const code = formData.get("pre-authorized_code") as string;
        const user_pin = formData.get("user_pin") as string;

        // 1. Validate the grant type
        if (grant_type !== "urn:ietf:params:oauth:grant-type:pre-authorized_code") {
            return NextResponse.json(
                { error: "unsupported_grant_type" },
                { status: 400 },
            );
        }

        // 2. Validate the pre-authorized code
        const authCode = await getAuthorizationCode(code);
        if (!authCode) {
            return NextResponse.json(
                {
                    error: "invalid_grant",
                    error_description: "Invalid or expired code",
                },
                { status: 400 },
            );
        }

        // 3. Validate the PIN (tx_code)
        const expectedTxCode = (global as any).txCodeStore?.get(code);
        if (expectedTxCode !== user_pin) {
            return NextResponse.json(
                { error: "invalid_grant", error_description: "Invalid PIN" },
                { status: 400 },
            );
        }

        // 4. Generate access token and c_nonce
        const accessToken = uuidv4();
        const cNonce = uuidv4();
        const cNonceExpiresAt = new Date(Date.now() + 5 * 60 * 1000); // 5 minutes

        // 5. Create a new issuance session
        const userData = (global as any).userDataStore?.get(code);
        await createIssuanceSession(
            uuidv4(),
            authCode.id,
            accessToken,
            cNonce,
            cNonceExpiresAt,
            userData,
        );

        // 6. Mark the code as used and clean up temporary data
        await markAuthorizationCodeAsUsed(code);
        (global as any).txCodeStore?.delete(code);
        (global as any).userDataStore?.delete(code);

        // 7. Return the access token response
        return NextResponse.json({
            access_token: accessToken,
            token_type: "Bearer",
            expires_in: 3600, // 1 hour
            c_nonce: cNonce,
            c_nonce_expires_in: 300, // 5 minutes
        });
    } catch (error) {
        console.error("Token endpoint error:", error);
        return NextResponse.json({ error: "server_error" }, { status: 500 });
    }
}
```

Passaggi chiave in questo endpoint:

1. **Convalida del tipo di grant:** si assicura che il wallet stia usando il tipo di grant
   `pre-authorized_code` corretto.
2. **Convalida del codice:** controlla che il `pre-authorized_code` esista nel database,
   non sia scaduto e non sia stato usato prima.
3. **Convalida del PIN:** confronta lo `user_pin` del wallet con il `tx_code` che abbiamo
   archiviato in precedenza per garantire che l'utente abbia autorizzato la transazione.
4. **Generazione di token:** crea un `access_token` sicuro e un `c_nonce` (credential
   nonce), che è un valore monouso per prevenire attacchi di replay sull'endpoint della
   credenziale.
5. **Creazione della sessione:** crea un nuovo record `issuance_sessions` nel database,
   collegando l'[access token](https://www.corbado.com/glossary/access-token) ai dati dell'utente.
6. **Contrassegno del codice come usato:** per evitare che la stessa offerta venga usata
   due volte, contrassegna il `pre-authorized_code` come usato.
7. **Restituzione del token:** restituisce l'`access_token` e il `c_nonce` al wallet.

#### 4.5.3 `/api/issue/credential`: emettere la credenziale firmata

Questo è l'endpoint finale e più importante. Il wallet usa l'access token che ha ricevuto
dall'endpoint `/token` per effettuare una richiesta `POST` autenticata a questa route. Il
compito di questo endpoint è eseguire la convalida finale, creare la credenziale firmata
crittograficamente e restituirla al wallet.

Crea il file `src/app/api/issue/credential/route.ts` con il seguente contenuto:

```typescript
// src/app/api/issue/credential/route.ts
import { NextRequest, NextResponse } from "next/server";
import { v4 as uuidv4 } from "uuid";
import {
    getIssuanceSessionByToken,
    updateIssuanceSession,
    createIssuedCredential,
    getActiveIssuerKey,
} from "@/lib/database";
import {
    createJWTVerifiableCredential,
    importIssuerKeyPair,
    generateIssuerDid,
} from "@/lib/crypto";

export async function POST(request: NextRequest) {
    try {
        // 1. Validate the Bearer token
        const authHeader = request.headers.get("authorization");
        const accessToken = authHeader?.substring(7);
        const session = await getIssuanceSessionByToken(accessToken);

        if (!session) {
            return NextResponse.json({ error: "invalid_token" }, { status: 401 });
        }

        // 2. Get the user data from the session
        const userData = session.user_data;
        if (!userData) {
            return NextResponse.json({ error: "missing_user_data" }, { status: 400 });
        }

        // 3. Get the active issuer key
        const issuerKey = await getActiveIssuerKey();
        if (!issuerKey) {
            // In a real application, you would have a more robust key management system.
            // For this demo, we can generate a key on the fly if one doesn't exist.
            // This part is omitted for brevity but is in the repository.
            return NextResponse.json(
                {
                    error: "server_error",
                    error_description: "Failed to get issuer key",
                },
                { status: 500 },
            );
        }

        // 4. Create the JWT-VC
        const issuerDid = generateIssuerDid();
        const keyPair = await importIssuerKeyPair(
            issuerKey.key_id,
            issuerKey.public_key,
            issuerKey.private_key,
            issuerDid,
        );
        const subjectId = `did:example:${uuidv4()}`;
        const credentialData = await createJWTVerifiableCredential(
            userData,
            keyPair,
            subjectId,
            process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000",
        );

        // 5. Store the issued credential in the database
        await createIssuedCredential(/* ... credential details ... */);
        await updateIssuanceSession(session.id, "credential_issued");

        // 6. Return the signed credential
        return NextResponse.json({
            format: "jwt_vc",
            credential: credentialData,
            c_nonce: uuidv4(), // A new nonce for subsequent requests
            c_nonce_expires_in: 300,
        });
    } catch (error) {
        console.error("Credential endpoint error:", error);
        return NextResponse.json({ error: "server_error" }, { status: 500 });
    }
}
```

Passaggi chiave in questo endpoint:

1. **Convalida del token:** controlla la presenza di un token `Bearer` valido
   nell'intestazione `Authorization` e lo usa per cercare la sessione di emissione attiva.
2. **Recupero dei dati utente:** recupera i dati dei claim dell'utente, che sono stati
   archiviati nella sessione quando il token è stato creato.
3. **Caricamento della chiave dell'issuer:** carica la chiave di firma attiva dell'issuer
   dal database. In uno scenario reale, questa sarebbe gestita da un sistema di gestione
   delle chiavi sicuro.
4. **Creazione della credenziale:** chiama il nostro helper
   `createJWTVerifiableCredential` da `src/lib/crypto.ts` per costruire e firmare la
   JWT-VC.
5. **Registrazione dell'emissione:** salva un record della credenziale emessa nel database
   per scopi di auditing e revoca.
6. **Restituzione della credenziale:** restituisce la credenziale firmata al wallet in una
   risposta JSON. Il wallet è quindi responsabile della sua archiviazione sicura.

## 5. Esecuzione dell'Issuer e passi successivi

Hai ora un'implementazione completa e end-to-end di un issuer di credenziali digitali.
Ecco come eseguirlo localmente e cosa devi considerare per portarlo da una
proof-of-concept a un'applicazione pronta per la produzione.

### 5.1 Come eseguire l'esempio

1. **Clona il repository:**

    ```bash
    git clone https://github.com/corbado/digital-credentials-example.git
    cd digital-credentials-example
    ```

2. **Installa le dipendenze:**

    ```bash
    npm install
    ```

3. **Avvia il database:** assicurati che Docker sia in esecuzione, quindi avvia il
   container MySQL:

    ```bash
    docker-compose up -d
    ```

4. **Configura l'ambiente ed esegui il tunnel:** questo è il passaggio più critico per i
   test locali. Poiché il tuo wallet mobile deve connettersi alla tua macchina di sviluppo
   tramite Internet, devi esporre il tuo server locale con un URL HTTPS pubblico. Per
   questo, useremo `ngrok`.

    a. **Avvia ngrok:**

    ```bash
    ngrok http 3000
    ```

    b. **Copia l'URL HTTPS** dall'output di
    [ngrok](https://www.corbado.com/blog/multi-device-passkey-login-corbado-ngrok) (ad esempio
    `https://stringa-casuale.ngrok.io`). c. **Crea un file `.env.local`** e imposta l'URL:

    ```
    NEXT_PUBLIC_BASE_URL=https://<your-ngrok-url>
    ```

5. **Esegui l'applicazione:**

    ```bash
    npm run dev
    ```

    Apri il tuo browser all'indirizzo `http://localhost:3000/issue`. Ora puoi compilare il
    modulo e il codice QR generato punterà correttamente al tuo URL pubblico di
    [ngrok](https://www.corbado.com/blog/multi-device-passkey-login-corbado-ngrok), consentendo al tuo wallet
    mobile di connettersi e ricevere la credenziale.

### 5.2 L'importanza di HTTPS e `ngrok`

I protocolli delle credenziali digitali sono costruiti con la sicurezza come massima
priorità. Per questo motivo, i wallet si rifiuteranno quasi sempre di connettersi a un
issuer tramite una connessione non sicura (`http://`). L'intero processo si basa su una
connessione **HTTPS** sicura, abilitata da un **certificato SSL**.

Un servizio di tunnel come `ngrok` risolve entrambi i problemi creando un URL HTTPS
pubblico e sicuro (con un certificato SSL valido) che inoltra tutto il traffico al tuo
server di sviluppo locale.\
I wallet richiedono HTTPS e si rifiuteranno di connettersi a endpoint non sicuri
(`http://`). Questo è uno strumento essenziale per testare qualsiasi servizio web che deve
interagire con dispositivi mobili o webhook esterni.

### 5.3 Cosa non è trattato in questo tutorial

Questo esempio è intenzionalmente focalizzato sul flusso di emissione principale per
renderlo facile da capire. I seguenti argomenti sono considerati fuori dall'ambito di
applicazione:

- **Sicurezza pronta per la produzione:** l'issuer è a scopo educativo. Un sistema di
  produzione richiederebbe un Key Management System (KMS) sicuro invece di archiviare le
  chiavi in un database, una solida gestione degli errori, il rate-limiting e una
  registrazione di audit completa.
- **Revoca delle credenziali:** questa guida non implementa un meccanismo per revocare le
  credenziali emesse.\
  Mentre lo schema include un flag `revoked` per un uso futuro, qui non viene fornita
  alcuna logica di revoca.
- **Flusso Authorization Code:** ci siamo concentrati esclusivamente sul flusso
  `pre-authorized_code`. Un'implementazione completa del flusso `authorization_code`
  richiederebbe una schermata di consenso dell'utente e una logica
  [OAuth 2.0](https://www.corbado.com/glossary/oauth2) più complessa.
- **Gestione degli utenti:** la guida non include alcuna
  [autenticazione](https://www.corbado.com/it/blog/come-passare-autenticazione-completamente-passwordless) o
  gestione degli utenti per l'issuer stesso. Si presume che l'utente sia già autenticato e
  autorizzato a ricevere una credenziale.

## 6. Conclusione

Ecco fatto! Con poche pagine di codice, ora abbiamo un issuer di credenziali digitali
completo e end-to-end che:

1. Fornisce un frontend di facile utilizzo per la richiesta di credenziali.
2. Implementa il flusso completo OpenID4VCI `pre-authorized_code`.
3. Espone tutti gli endpoint di discovery necessari per l'interoperabilità del wallet.
4. Genera e firma una credenziale verificabile JWT sicura e conforme agli standard.

Sebbene questa guida fornisca una solida base, un issuer pronto per la produzione
richiederebbe funzionalità aggiuntive come una solida gestione delle chiavi, archiviazione
persistente invece di archivi in memoria, revoca delle credenziali e un completo
rafforzamento della sicurezza.\
Anche la compatibilità dei wallet varia; si consiglia lo Sphereon Wallet per i test, ma
altri wallet potrebbero non supportare il flusso pre-autorizzato come implementato qui.
Tuttavia, i blocchi di costruzione principali e il flusso di interazione rimarrebbero gli
stessi. Seguendo questi schemi, puoi costruire un issuer sicuro e interoperabile per
qualsiasi tipo di credenziale digitale.

## 7. Risorse

Ecco alcune delle risorse chiave, delle specifiche e degli strumenti usati o citati in
questo tutorial:

- **Repository del progetto:**
    - [Codice sorgente completo su GitHub](https://github.com/corbado/digital-credentials-example)

- **Specifiche chiave:**
    - [OpenID for Verifiable Credential Issuance (OpenID4VCI)](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html):
      il protocollo di emissione principale.
    - [W3C Verifiable Credentials Data Model](https://www.w3.org/TR/vc-data-model/): lo
      standard fondamentale per le VC.
    - [Il metodo `did:web`](https://w3c-ccg.github.io/did-method-web/): il metodo DID
      usato per la chiave pubblica del nostro issuer.

- **Strumenti:**
    - [Sphereon Wallet](https://sphereon.com/wallet/): il wallet di prova usato in questa
      guida.
    - ngrok: per creare un tunnel sicuro verso il tuo ambiente di sviluppo locale.

- **Librerie:**
    - Next.js: il framework React per la creazione del frontend e del backend.
    - [jose](https://github.com/panva/jose): per la creazione e la firma di JSON Web Token
      (JWT).
    - [mysql2](https://github.com/sidorares/node-mysql2): il client MySQL per
      [Node.js](https://www.corbado.com/blog/nodejs-passkeys).
