Impara a creare da zero un verifier di credenziali digitali usando Next.js, OpenID4VP e ISO mDoc. Questa guida passo-passo per sviluppatori mostra come creare un verifier in grado di richiedere, ricevere e convalidare patenti di guida mobili e altre crede
Amine
Created: August 20, 2025
Updated: August 21, 2025
See the original blog version in English here.
Dimostrare la propria identità online è una sfida costante, che porta a fare affidamento sulle password e alla condivisione di documenti sensibili su canali non sicuri. Questo ha reso la verifica dell'identità per le aziende un processo lento, costoso e soggetto a frodi. Le credenziali digitali offrono un nuovo approccio, ridando agli utenti il controllo dei propri dati. Sono l'equivalente digitale di un wallet fisico, contenente di tutto, dalla patente di guida alla laurea, ma con i vantaggi aggiuntivi di essere crittograficamente sicure, rispettose della privacy e verificabili all'istante.
Questa guida offre agli sviluppatori un tutorial pratico e passo-passo per costruire un verifier per le credenziali digitali. Sebbene gli standard esistano, ci sono poche indicazioni su come implementarli. Questo tutorial colma questa lacuna, mostrando come costruire un verifier utilizzando l'API nativa per le credenziali digitali del browser, OpenID4VP per il protocollo di presentazione e ISO mDoc (ad es., la patente di guida mobile) come formato della credenziale.
Il risultato finale sarà un'applicazione Next.js semplice ma funzionale, in grado di richiedere, ricevere e verificare una credenziale digitale da un wallet mobile compatibile.
Ecco una rapida anteprima dell'applicazione finale in azione. Il processo prevede quattro passaggi principali:
Passaggio 1: Pagina iniziale L'utente arriva sulla pagina iniziale e clicca su "Verifica con identità digitale" per avviare il processo.
Passaggio 2: Prompt di fiducia Il browser chiede all'utente di dare fiducia. L'utente clicca su "Continua" per procedere.
Passaggio 3: Scansione del codice QR Viene visualizzato un codice QR, che l'utente scansiona con la sua applicazione wallet compatibile.
Passaggio 4: Credenziale decodificata Dopo la verifica riuscita, l'applicazione mostra i dati della credenziale decodificata.
Recent Articles
📝
Come creare un Issuer di credenziali digitali (Guida per sviluppatori)
📝
Come creare un Verifier di credenziali digitali (Guida per sviluppatori)
📖
Chiave Residente WebAuthn: Credenziali Individuabili come Passkey
🔑
Accesso con badge fisico e passkey: guida tecnica
🔑
Rendere obbligatoria la MFA e passare alle Passkey: Best Practice
La magia dietro le credenziali digitali risiede in un modello a "triangolo della fiducia" semplice ma potente, che coinvolge tre attori chiave:
Quando un utente vuole accedere a un servizio, presenta la credenziale dal suo wallet. Il verifier può quindi verificarne istantaneamente l'autenticità senza dover contattare direttamente l'issuer originale.
Perché questo ecosistema di identità decentralizzata possa prosperare, il ruolo del verifier è assolutamente cruciale. Sono i guardiani di questa nuova infrastruttura di fiducia, coloro che consumano le credenziali e le rendono utili nel mondo reale. Come illustra il diagramma sottostante, un verifier completa il triangolo della fiducia richiedendo, ricevendo e convalidando una credenziale dal holder.
Se sei uno sviluppatore, costruire un servizio per eseguire questa verifica è una competenza fondamentale per la prossima generazione di applicazioni sicure e incentrate sull'utente. Questa guida è progettata per accompagnarti esattamente in questo processo. Tratteremo tutto ciò che devi sapere per costruire il tuo verifier di credenziali verificabili, dai concetti e standard di base ai dettagli di implementazione passo-passo per la convalida delle firme e il controllo dello stato delle credenziali.
Vuoi andare avanti? Puoi trovare il progetto completo di questo tutorial su GitHub. Sentiti libero di clonarlo e provarlo tu stesso: https://github.com/corbado/digital-credentials-example
Iniziamo.
Prima di iniziare, assicurati di avere:
Ora esamineremo in dettaglio ciascuno di questi prerequisiti, iniziando dagli standard e protocolli che sono alla base di questo verifier basato su mdoc.
Il nostro verifier è costruito per quanto segue:
Standard / Protocollo | Descrizione |
---|---|
W3C VC | Il W3C Verifiable Credentials Data Model. Definisce la struttura standard per le credenziali digitali, inclusi claim, metadati e prove. |
SD-JWT | Selective Disclosure per JWT. Un formato per VC basato su JSON Web Token che consente ai titolari di divulgare selettivamente solo specifici claim da una credenziale, migliorando la privacy. |
ISO mDoc | ISO/IEC 18013-5. Lo standard internazionale per le patenti di guida mobili (mDL) e altri documenti d'identità mobili, che definisce le strutture dati e i protocolli di comunicazione per l'uso offline e online. |
OpenID4VP | OpenID for Verifiable Presentations. Un protocollo di presentazione interoperabile basato su OAuth 2.0. Definisce come un verifier richiede le credenziali e come il wallet di un holder le presenta. |
Per questo tutorial, usiamo specificamente:
Nota sull'ambito: Sebbene introduciamo brevemente W3C VC e SD-JWT per fornire un contesto più ampio, questo tutorial implementa esclusivamente credenziali ISO mDoc tramite OpenID4VP. Le VC basate su W3C sono fuori dall'ambito di questo esempio.
Lo standard ISO/IEC 18013-5 mDoc definisce la struttura e la codifica per i documenti mobili come le patenti di guida mobili (mDL). Le credenziali mDoc sono codificate in CBOR, firmate crittograficamente e possono essere presentate digitalmente per la verifica. Il nostro verifier si concentrerà sulla decodifica e la convalida di queste credenziali mdoc.
OpenID4VP è un protocollo interoperabile per la richiesta e la presentazione di credenziali digitali, basato su OAuth 2.0 e OpenID Connect. In questa implementazione, OpenID4VP viene utilizzato per:
Ora che abbiamo una chiara comprensione degli standard e dei protocolli, dobbiamo scegliere lo stack tecnologico giusto per costruire il nostro verifier. Le nostre scelte sono progettate per garantire robustezza, una buona esperienza per lo sviluppatore e compatibilità con il moderno ecosistema web.
Useremo TypeScript sia per il nostro codice frontend che backend. Essendo un soprainsieme di JavaScript, aggiunge la tipizzazione statica, che aiuta a individuare gli errori precocemente, migliora la qualità del codice e rende più facili da gestire le applicazioni complesse. In un contesto sensibile alla sicurezza come la verifica delle credenziali, la sicurezza dei tipi è un enorme vantaggio.
Next.js è il nostro framework di scelta perché offre un'esperienza integrata e fluida per la creazione di applicazioni full-stack.
redirect_uri
per ricevere e verificare in modo sicuro la
risposta finale dal CMWallet.La nostra implementazione si basa su un insieme specifico di librerie per il frontend e il backend:
Nota su openid-client
: Verifier più avanzati e di livello produttivo potrebbero
utilizzare la libreria openid-client
per gestire direttamente il protocollo OpenID4VP
sul backend, abilitando funzionalità come un redirect_uri
dinamico. In un flusso
OpenID4VP guidato dal server con un redirect_uri
, openid-client
verrebbe utilizzato
per analizzare e convalidare direttamente le risposte vp_token
. Per questo tutorial,
stiamo usando un flusso più semplice, mediato dal browser, che non lo richiede, rendendo
il processo più facile da capire.
Questo stack tecnologico garantisce un'implementazione del verifier robusta, type-safe e scalabile, focalizzata sull'API per le credenziali digitali del browser e sul formato delle credenziali ISO mDoc.
Per testare il tuo verifier, hai bisogno di un wallet mobile in grado di interagire con l'API per le credenziali digitali del browser.
Useremo il CMWallet, un robusto wallet di test compatibile con OpenID4VP per Android.
Come installare CMWallet (Android):
Nota: Installa file APK solo da fonti di cui ti fidi. Il link fornito proviene dal repository ufficiale del progetto.
Prima di immergerci nell'implementazione, è essenziale comprendere i concetti crittografici che sono alla base delle credenziali verificabili. È questo che le rende "verificabili" e affidabili.
Nel suo nucleo, una credenziale verificabile è un insieme di attestazioni (come nome, data di nascita, ecc.) che è stato firmato digitalmente da un issuer. Una firma digitale offre due garanzie cruciali:
Le firme digitali vengono create utilizzando la crittografia a chiave pubblica/privata (chiamata anche crittografia asimmetrica). Ecco come funziona nel nostro contesto:
Nota sui DID: In questo tutorial, non risolviamo le chiavi dell'issuer tramite DID. In produzione, gli issuer esporrebbero tipicamente le chiavi pubbliche tramite DID o altri endpoint autorevoli, che il verifier utilizzerebbe per la convalida crittografica.
Le credenziali verificabili sono
spesso formattate come JSON Web Token (JWT). Un JWT è un modo compatto e sicuro per
gli URL di rappresentare le attestazioni da trasferire tra due parti. Un JWT firmato (noto
anche come JWS) ha tre parti separate da punti (.
):
alg
).vc
), inclusi
issuer
, credentialSubject
, ecc.// Esempio di una struttura JWT [Header].[Payload].[Signature]
Nota: Le credenziali verificabili basate su JWT sono fuori dall'ambito di questo post del blog. Questa implementazione si concentra sulle credenziali ISO mDoc e OpenID4VP, non sulle credenziali verificabili W3C o basate su JWT.
Non è sufficiente per un verifier sapere che una credenziale è valida; deve anche sapere che la persona che presenta la credenziale è il legittimo titolare. Questo impedisce a qualcuno di usare una credenziale rubata.
Questo problema viene risolto utilizzando una presentazione verificabile (VP). Una VP è un involucro attorno a una o più VC che è firmato dal titolare stesso.
Il flusso è il seguente:
Il nostro verifier deve quindi eseguire due controlli di firma separati:
Questo controllo a due livelli garantisce sia l'autenticità della credenziale che l'identità della persona che la presenta, creando un modello di fiducia robusto e sicuro.
Nota: Il concetto di presentazioni verificabili come definito nell'ecosistema W3C VC
è fuori dall'ambito di questo post del blog. Il termine
presentazione verificabile qui si riferisce alla
risposta OpenID4VP vp_token
, che si comporta in modo simile a una VP W3C ma si basa
sulla semantica ISO mDoc piuttosto che sul modello di firma JSON-LD
del W3C. Questa guida si concentra sulle credenziali ISO mDoc e OpenID4VP, non sulle
presentazioni verificabili W3C o sulla loro validazione della firma.
La nostra architettura del verifier utilizza l'API per le credenziali digitali integrata nel browser come intermediario sicuro per connettere la nostra applicazione web con il CMWallet mobile dell'utente. Questo approccio semplifica il flusso lasciando che sia il browser a gestire la visualizzazione nativa del codice QR e la comunicazione con il wallet.
navigator.credentials.get()
del browser, ricevere il risultato e inoltrarlo al nostro
backend per la verifica.openid4vp
e genera nativamente un codice QR.
Attende quindi che il wallet restituisca una risposta.Ecco un diagramma di sequenza che illustra il flusso completo e accurato:
Spiegazione del flusso:
/api/verify/start
), che genera un oggetto di richiesta contenente la query e un
nonce, quindi lo restituisce.navigator.credentials.get()
con
l'oggetto di richiesta.openid4vp
e
visualizza nativamente un codice QR. La promessa .get()
è ora in attesa.Nota: Questo flusso con codice QR avviene sui browser desktop. Sui browser mobili
(Android Chrome con flag sperimentale abilitato), il browser può comunicare direttamente
con i wallet compatibili sullo stesso dispositivo,
eliminando la necessità di scansionare il codice QR. Per abilitare questa funzione su
Android Chrome, vai su chrome://flags#web-identity-digital-credentials
e imposta il
flag su "Enabled".
.get()
originale sul frontend si risolve finalmente, consegnando il payload della
presentazione./api/verify/finish
del nostro backend. Il backend convalida il
nonce e la credenziale.Ora che abbiamo una solida comprensione degli standard, dei protocolli e del flusso architetturale, possiamo iniziare a costruire il nostro verifier.
Segui passo passo o usa il codice finale
Ora esamineremo la configurazione e l'implementazione del codice passo dopo passo. Se preferisci passare direttamente al prodotto finito, puoi clonare il progetto completo dal nostro repository GitHub ed eseguirlo localmente.
git clone https://github.com/corbado/digital-credentials-example.git
Innanzitutto, inizializzeremo un nuovo progetto Next.js, installeremo le dipendenze necessarie e avvieremo il nostro database.
Apri il terminale, naviga nella 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.
npx create-next-app@latest . --ts --eslint --tailwind --app --src-dir --import-alias "@/*" --use-npm
Questo comando crea lo scheletro di una nuova applicazione Next.js nella tua directory corrente.
Successivamente, dobbiamo installare le librerie che gestiranno la decodifica CBOR, le connessioni al database e la generazione di UUID.
npm install cbor-web mysql2 uuid @types/uuid
Questo comando installa:
cbor-web
: Per la decodifica del payload della credenziale mdoc.mysql2
: Il client MySQL per il nostro database.uuid
: Per generare stringhe di challenge uniche.@types/uuid
: Tipi TypeScript per la libreria uuid
.Il nostro backend richiede un database MySQL per memorizzare i dati della sessione OIDC,
garantendo che ogni flusso di verifica sia sicuro e stateful. Abbiamo incluso un file
docker-compose.yml
per rendere questo processo semplice.
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:
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:
-- 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) );
Una volta che entrambi i file sono al loro posto, apri il terminale nella radice del progetto ed esegui:
docker-compose up -d
Questo comando avvierà un container MySQL in background.
La nostra applicazione Next.js è strutturata per separare le responsabilità tra frontend e backend, anche se fanno parte dello stesso progetto.
src/app/page.tsx
): Una singola pagina React che
avvia il flusso di verifica e visualizza il risultato. Interagisce con l'API per le
credenziali digitali del browser.src/app/api/verify/...
):
start/route.ts
: Genera la richiesta OpenID4VP e un nonce di
sicurezza.finish/route.ts
: Riceve la presentazione dal wallet (tramite il browser),
convalida il nonce e decodifica la credenziale.src/lib/
):
database.ts
: Gestisce tutte le interazioni con il database (creazione di
challenge, verifica delle sessioni).crypto.ts
: Gestisce la decodifica della credenziale mDoc basata su CBOR.Ecco un diagramma che illustra l'architettura interna:
Il nostro frontend è intenzionalmente leggero. La sua responsabilità principale è agire come trigger rivolto all'utente per il flusso di verifica e comunicare sia con il nostro backend che con le capacità native di gestione delle credenziali del browser. Non contiene alcuna logica di protocollo complessa; tutto ciò è delegato.
Nello specifico, il frontend gestirà quanto segue:
/api/verify/start
e riceve un
payload JSON strutturato (protocol
, request
, state
) che descrive esattamente cosa
il wallet dovrebbe presentare.navigator.credentials.get()
, che renderizza un codice QR nativo e attende la risposta
del wallet./api/verify/finish
in una richiesta POST per la convalida finale lato server.La logica principale si trova nella funzione startVerification
:
// src/app/page.tsx const startVerification = async () => { setLoading(true); setVerificationResult(null); try { // 1. Controlla se il browser supporta l'API if (!navigator.credentials?.get) { throw new Error("Il browser non supporta la Credential API."); } // 2. Chiedi al nostro backend un oggetto di richiesta const res = await fetch("/api/verify/start"); const { protocol, request } = await res.json(); // 3. Passa quell'oggetto al browser – questo attiva il codice QR nativo const credential = await (navigator.credentials as any).get({ mediation: "required", digital: { requests: [ { protocol, // "openid4vp" data: request, // contiene dcql_query, nonce, ecc. }, ], }, }); // 4. Inoltra la risposta del wallet (dal browser) al nostro endpoint di fine per i controlli lato server const verifyRes = await fetch("/api/verify/finish", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(credential), }); const result = await verifyRes.json(); if (verifyRes.ok && result.verified) { setVerificationResult(`Successo: ${result.message}`); } else { throw new Error(result.message || "Verifica fallita."); } } catch (err) { setVerificationResult(`Errore: ${(err as Error).message}`); } finally { setLoading(false); } };
Questa funzione mostra i quattro passaggi chiave della logica del frontend: controllo del supporto API, recupero della richiesta dal backend, chiamata all'API del browser e invio del risultato per la verifica. Il resto del file è boilerplate React standard per la gestione dello stato e il rendering dell'interfaccia utente, che puoi visualizzare nel repository GitHub.
digital
e mediation: 'required'
?#Potresti notare che la nostra chiamata a navigator.credentials.get()
sembra diversa da
esempi più semplici. Questo perché stiamo aderendo rigorosamente alla
specifica ufficiale dell'API per le credenziali digitali del W3C.
Membro digital
: La specifica richiede che tutte le richieste di credenziali
digitali siano annidate all'interno di un oggetto digital
. Questo fornisce uno spazio
dei nomi chiaro e standardizzato per questa API, distinguendola da altri tipi di
credenziali (come password
o federated
) e consentendo estensioni future senza
conflitti.
mediation: 'required'
: Questa opzione è una caratteristica cruciale per la
sicurezza e l'esperienza dell'utente. Impone che l'utente debba interagire attivamente
con un prompt (ad es., una scansione biometrica, l'inserimento di un PIN o una schermata
di consenso) per approvare la richiesta di credenziali. Senza di essa, un sito web
potrebbe potenzialmente tentare di accedere silenziosamente alle credenziali in
background, il che rappresenta un rischio significativo per la privacy. Richiedendo la
mediazione, garantiamo che l'utente sia sempre in controllo e dia il consenso esplicito
per ogni transazione.
Con l'interfaccia utente React pronta, ora abbiamo bisogno di due API routes che svolgano il lavoro pesante sul server:
/api/verify/start
– costruisce una richiesta OpenID4VP, salva una challenge
monouso in MySQL e restituisce tutto al browser./api/verify/finish
– riceve la risposta del wallet, convalida la challenge,
verifica e decodifica la credenziale e infine restituisce un risultato JSON conciso
all'interfaccia utente./api/verify/start
: Generare la richiesta OpenID4VP#// src/app/api/verify/start/route.ts import { NextResponse } from "next/server"; import { v4 as uuidv4 } from "uuid"; import { createChallenge, cleanupExpiredChallenges } from "@/lib/database"; export async function GET() { // 1️⃣ Crea un nonce (challenge) casuale e di breve durata const challenge = uuidv4(); const challengeId = uuidv4(); const expiresAt = new Date(Date.now() + 5 * 60 * 1000); await createChallenge(challengeId, challenge, expiresAt); cleanupExpiredChallenges().catch(console.error); // 2️⃣ Costruisci una query DCQL che descrive *cosa* vogliamo const dcqlQuery = { credentials: [ { id: "cred1", format: "mso_mdoc", meta: { doctype_value: "eu.europa.ec.eudi.pid.1" }, claims: [ { path: ["eu.europa.ec.eudi.pid.1", "family_name"] }, { path: ["eu.europa.ec.eudi.pid.1", "given_name"] }, { path: ["eu.europa.ec.eudi.pid.1", "birth_date"] }, ], }, ], }; // 3️⃣ Restituisci un oggetto che il browser può passare a navigator.credentials.get() return NextResponse.json({ protocol: "openid4vp", // dice al browser quale protocollo di wallet usare request: { dcql_query: dcqlQuery, // COSA presentare nonce: challenge, // anti-replay response_type: "vp_token", response_mode: "dc_api", // il wallet farà una POST direttamente a /finish }, state: { credential_type: "mso_mdoc", // conservato per controlli successivi nonce: challenge, challenge_id: challengeId, }, }); }
Parametri chiave
• nonce
– sfida crittografica che lega
richiesta e risposta (previene il replay). • dcql_query
– Un oggetto che descrive
le attestazioni esatte di cui abbiamo bisogno. Per questa guida, usiamo una struttura
dcql_query
ispirata alle recenti bozze del Digital Credential Query Language, anche se
non è ancora uno standard finalizzato. • state
– JSON arbitrario restituito dal
wallet in modo che possiamo cercare il record nel database.
Il file src/lib/database.ts
racchiude le operazioni MySQL di base per le challenge e le
sessioni di verifica (inserimento, lettura, contrassegno come usato). Mantenere questa
logica in un unico modulo rende facile sostituire il datastore in seguito.
/api/verify/finish
: Convalidare e decodificare la presentazione#// src/app/api/verify/finish/route.ts import { NextResponse, NextRequest } from "next/server"; import { v4 as uuidv4 } from "uuid"; import { getChallenge, markChallengeAsUsed, createVerificationSession, updateVerificationSession, } from "@/lib/database"; import { decodeDigitalCredential, decodeAllNamespaces } from "@/lib/crypto"; export async function POST(request: NextRequest) { const body = await request.json(); // 1️⃣ Estrai i pezzi della presentazione verificabile const vpTokenMap = body.vp_token ?? body.data?.vp_token; const state = body.state; const mdocToken = vpTokenMap?.cred1; // abbiamo richiesto questo ID in dcqlQuery if (!vpTokenMap || !state || !mdocToken) { return NextResponse.json( { verified: false, message: "Risposta malformata" }, { status: 400 }, ); } // 2️⃣ Validazione della challenge monouso const stored = await getChallenge(state.nonce); if (!stored) { return NextResponse.json( { verified: false, message: "Challenge non valida o scaduta" }, { status: 400 }, ); } const sessionId = uuidv4(); await createVerificationSession(sessionId, stored.id); // 3️⃣ Controlli crittografici (pseudo) – sostituire con una vera validazione mDL in produzione // In un'applicazione reale, useresti una libreria dedicata per eseguire una validazione crittografica completa // della firma mdoc rispetto alla chiave pubblica dell'issuer. const isValid = mdocToken.length > 0; if (!isValid) { await updateVerificationSession(sessionId, "failed", { reason: "validazione mdoc fallita", }); return NextResponse.json( { verified: false, message: "Validazione della credenziale fallita" }, { status: 400 }, ); } // 4️⃣ Decodifica il payload del mobile-DL (mdoc) in JSON leggibile const decoded = await decodeDigitalCredential(mdocToken); const readable = decodeAllNamespaces(decoded)["eu.europa.ec.eudi.pid.1"]; await markChallengeAsUsed(state.nonce); await updateVerificationSession(sessionId, "verified", { readable }); return NextResponse.json({ verified: true, message: "Credenziale mdoc verificata con successo!", credentialData: readable, sessionId, }); }
Campi importanti nella risposta del wallet
• vp_token
– mappa che contiene ciascuna credenziale restituita dal wallet. Per
la nostra demo prendiamo vp_token.cred1
. • state
– eco del blob che abbiamo
fornito in /start
; contiene il nonce
in modo da poter cercare il record nel
database. • mdocToken
– una struttura CBOR codificata in Base64URL che rappresenta
l'ISO mDoc.
Quando il verifier riceve una credenziale mdoc dal browser, si tratta di una stringa
Base64URL contenente dati binari codificati in CBOR. Per estrarre le attestazioni
effettive, l'endpoint finish
esegue un processo di decodifica a più passaggi utilizzando
funzioni di supporto da src/lib/crypto.ts
.
La funzione decodeDigitalCredential
gestisce la conversione dalla stringa codificata a
un oggetto utilizzabile:
// src/lib/crypto.ts export async function decodeDigitalCredential(encodedCredential: string) { // 1. Converti Base64URL in Base64 standard const base64UrlToBase64 = (input: string) => { let base64 = input.replace(/-/g, "+").replace(/_/g, "/"); const pad = base64.length % 4; if (pad) base64 += "=".repeat(4 - pad); return base64; }; const base64 = base64UrlToBase64(encodedCredential); // 2. Decodifica Base64 in binario const binaryString = atob(base64); const byteArray = Uint8Array.from(binaryString, (char) => char.charCodeAt(0)); // 3. Decodifica CBOR const decoded = await cbor.decodeFirst(byteArray); return decoded; }
cbor-web
per decodificare i dati binari in
un oggetto JavaScript strutturato.La funzione decodeAllNamespaces
elabora ulteriormente l'oggetto CBOR decodificato per
estrarre le attestazioni effettive dai namespace pertinenti:
// src/lib/crypto.ts export function decodeAllNamespaces(jsonObj) { const decoded = {}; try { jsonObj.documents.forEach((doc, idx) => { // 1) issuerSigned.nameSpaces: const issuerNS = doc.issuerSigned?.nameSpaces || {}; Object.entries(issuerNS).forEach(([nsName, entries]) => { if (!decoded[nsName]) decoded[nsName] = {}; (entries as any[]).forEach((entry) => { const bytes = Uint8Array.from(entry.value); const decodedEntry = cbor.decodeFirstSync(bytes); Object.assign(decoded[nsName], decodedEntry); }); }); // 2) deviceSigned.nameSpaces (se presente): const deviceNS = doc.deviceSigned?.nameSpaces; if (deviceNS?.value?.data) { const bytes = Uint8Array.from(deviceNS.value); decoded[`deviceSigned_ns_${idx}`] = cbor.decodeFirstSync(bytes); } }); } catch (e) { console.error(e); } return decoded; }
eu.europa.ec.eudi.pid.1
) per estrarre i valori
effettivi delle attestazioni (come nome, data di nascita, ecc.).Dopo aver eseguito questi passaggi, l'endpoint finish ottiene un oggetto leggibile dall'uomo contenente le attestazioni dal mdoc, ad esempio:
{ "family_name": "Doe", "given_name": "John", "birth_date": "1990-01-01" }
Questo processo garantisce che il verifier possa estrarre in modo sicuro e affidabile le informazioni necessarie dalla credenziale mdoc per la visualizzazione e l'ulteriore elaborazione.
L'endpoint finish restituisce un oggetto JSON minimo al frontend:
{ "verified": true, "message": "Credenziale mdoc verificata con successo!", "credentialData": { "family_name": "Doe", "given_name": "John", "birth_date": "1990-01-01" } }
Il frontend riceve questa risposta in startVerification()
e la salva semplicemente nello
stato di React in modo da poter renderizzare una bella scheda di conferma o visualizzare
singole attestazioni – ad es. “Benvenuto, John Doe (nato il 1990-01-01)!”.
Ora hai un verifier completo e funzionante che utilizza le capacità native di gestione delle credenziali del browser. Ecco come eseguirlo localmente e cosa puoi fare per portarlo da una proof-of-concept a un'applicazione pronta per la produzione.
Clona il repository:
git clone https://github.com/corbado/digital-credentials-example.git cd digital-credentials-example
Installa le dipendenze:
npm install
Avvia il database: Assicurati che Docker sia in esecuzione sulla tua macchina, quindi avvia il container MySQL:
docker-compose up -d
Esegui l'applicazione:
npm run dev
Apri il browser all'indirizzo http://localhost:3000
e dovresti vedere l'interfaccia
utente del verifier. Ora puoi usare il tuo CMWallet per scansionare il codice QR e
completare il flusso di verifica.
Questo tutorial fornisce i mattoni fondamentali per un verifier. Per renderlo pronto per la produzione, dovresti implementare diverse funzionalità aggiuntive:
Validazione crittografica completa: L'implementazione attuale utilizza un controllo
segnaposto (mdocToken.length > 0
). In uno scenario reale, è necessario eseguire una
validazione crittografica completa della firma mdoc rispetto alla chiave pubblica
dell'issuer (ad es., risolvendo il loro DID o recuperando il loro
certificato di chiave pubblica). Per gli standard di risoluzione dei DID, fare
riferimento alla specifica W3C DID Resolution.
Controllo della revoca dell'issuer: Le credenziali possono essere revocate dall'issuer prima della loro data di scadenza. Un verifier di produzione deve controllare lo stato della credenziale interrogando una lista di revoca o un endpoint di stato fornito dall'issuer. La W3C Verifiable Credentials Status List fornisce lo standard per le liste di revoca delle credenziali.
Gestione robusta degli errori e sicurezza: Aggiungi una gestione completa degli errori, la validazione degli input, il rate-limiting sugli endpoint API e assicurati che tutte le comunicazioni avvengano tramite HTTPS (TLS) per proteggere i dati in transito. Le OWASP API Security Guidelines forniscono best practice complete per la sicurezza delle API.
Supporto per più tipi di credenziali: Estendi la logica per gestire diversi valori
di doctype
e formati di credenziali se prevedi di ricevere più della sola credenziale
PID dell'identità digitale europea (EUDI). Il
W3C Verifiable Credentials Data Model fornisce
specifiche complete sul formato delle VC.
Questo esempio è volutamente incentrato sul flusso principale mediato dal browser per renderlo facile da capire. I seguenti argomenti sono considerati fuori dall'ambito:
redirect_uri
o la
registrazione dinamica del client.Basandoti su queste fondamenta e incorporando questi passaggi successivi, puoi sviluppare un verifier robusto e sicuro in grado di fidarsi e convalidare le credenziali digitali nelle tue applicazioni.
Ecco fatto! Con meno di 250 righe di TypeScript, ora abbiamo un verifier end-to-end che:
In produzione, sostituiresti la validazione segnaposto con controlli completi ISO 18013-5, aggiungeresti controlli di revoca dell'issuer, rate-limiting, audit logging e, naturalmente, TLS end-to-end, ma i mattoni fondamentali rimangono esattamente gli stessi.
Ecco alcune delle risorse chiave, specifiche e strumenti utilizzati o citati in questo tutorial:
Repository del progetto:
Specifiche chiave:
Strumenti:
Librerie:
Related Articles
Table of Contents