Apprenez à construire un vérificateur d'identifiants numériques de A à Z avec Next.js, OpenID4VP et ISO mDoc. Ce guide pas à pas pour les développeurs montre comment créer un vérificateur capable de demander, recevoir et valider des permis de conduire mob
Amine
Created: August 20, 2025
Updated: August 21, 2025
See the original blog version in English here.
Prouver son identité en ligne est un défi constant. Cela nous conduit à dépendre des mots de passe et à partager des documents sensibles sur des canaux non sécurisés. Pour les entreprises, la vérification d'identité est devenue un processus lent, coûteux et sujet à la fraude. Les identifiants numériques (Digital Credentials) offrent une nouvelle approche qui redonne aux utilisateurs le contrôle de leurs données. Ils sont l'équivalent numérique d'un portefeuille physique, contenant tout, d'un permis de conduire à un diplôme universitaire, avec les avantages supplémentaires d'être sécurisés par cryptographie, de préserver la confidentialité et d'être vérifiables instantanément.
Ce guide propose aux développeurs un tutoriel pratique et détaillé pour construire un vérificateur d'identifiants numériques. Bien que les standards existent, il y a peu de documentation sur leur mise en œuvre. Ce tutoriel comble cette lacune en montrant comment construire un vérificateur en utilisant l'API native Digital Credential du navigateur, OpenID4VP pour le protocole de présentation, et le format ISO mDoc (par exemple, pour un permis de conduire mobile) comme format d'identifiant.
Le résultat final sera une application Next.js simple mais fonctionnelle, capable de demander, recevoir et vérifier un identifiant numérique à partir d'un portefeuille mobile compatible.
Voici un aperçu rapide de l'application finale en action. Le processus se déroule en quatre étapes principales :
Étape 1 : Page initiale L'utilisateur arrive sur la page initiale et clique sur « Vérifier avec l'identité numérique » pour lancer le processus.
Étape 2 : Demande de confiance Le navigateur demande à l'utilisateur de confirmer sa confiance. L'utilisateur clique sur « Continuer » pour poursuivre.
Étape 3 : Scan du QR Code Un QR code est affiché, que l'utilisateur scanne avec son application de portefeuille compatible.
Étape 4 : Identifiant décodé Après une vérification réussie, l'application affiche les données de l'identifiant décodé.
Recent Articles
📝
Comment construire un vérificateur d'identifiants numériques (Guide du développeur)
📝
Comment créer un émetteur d'identifiants numériques (Guide du développeur)
📖
Clé résidente WebAuthn : les informations d'identification détectables en tant que passkeys
🔑
Accès par badge physique et Passkeys : Guide technique
🔑
MFA obligatoire et transition vers les Passkeys : les bonnes pratiques
La magie des identifiants numériques repose sur un modèle simple mais puissant de « triangle de confiance » impliquant trois acteurs clés :
Lorsqu'un utilisateur souhaite accéder à un service, il présente l'identifiant depuis son portefeuille. Le vérificateur peut alors instantanément contrôler son authenticité sans avoir besoin de contacter directement l'émetteur d'origine.
Pour que cet écosystème d'identité décentralisée puisse prospérer, le rôle du vérificateur est absolument crucial. Ils sont les gardiens de cette nouvelle infrastructure de confiance, ceux qui consomment les identifiants et les rendent utiles dans le monde réel. Comme l'illustre le schéma ci-dessous, un vérificateur complète le triangle de confiance en demandant, recevant et validant un identifiant auprès du titulaire.
Si vous êtes développeur, construire un service pour effectuer cette vérification est une compétence fondamentale pour la prochaine génération d'applications sécurisées et centrées sur l'utilisateur. Ce guide est conçu pour vous accompagner précisément dans ce processus. Nous couvrirons tout ce que vous devez savoir pour construire votre propre vérificateur d'identifiants vérifiables, des concepts et standards de base aux détails de mise en œuvre pas à pas pour valider les signatures et vérifier le statut des identifiants.
Vous voulez aller plus vite ? Vous pouvez trouver le projet complet de ce tutoriel sur GitHub. N'hésitez pas à le cloner et à l'essayer vous-même : https://github.com/corbado/digital-credentials-example
C'est parti.
Avant de commencer, assurez-vous d'avoir :
Nous allons maintenant examiner en détail chacun de ces prérequis, en commençant par les standards et les protocoles qui sous-tendent ce vérificateur basé sur le mdoc.
Notre vérificateur est construit pour les éléments suivants :
Standard / Protocole | Description |
---|---|
W3C VC | Le modèle de données des identifiants vérifiables du W3C. Il définit la structure standard des identifiants numériques, y compris les déclarations (claims), les métadonnées et les preuves. |
SD-JWT | Divulgation sélective pour les JWT. Un format pour les VCs basé sur les JSON Web Tokens qui permet aux titulaires de ne divulguer que certaines déclarations d'un identifiant, améliorant ainsi la confidentialité. |
ISO mDoc | ISO/IEC 18013-5. Le standard international pour les permis de conduire mobiles (mDL) et autres identifiants mobiles, définissant les structures de données et les protocoles de communication pour une utilisation en ligne et hors ligne. |
OpenID4VP | OpenID for Verifiable Presentations. Un protocole de présentation interopérable basé sur OAuth 2.0. Il définit comment un vérificateur demande des identifiants et comment le portefeuille d'un titulaire les présente. |
Pour ce tutoriel, nous utilisons spécifiquement :
Note sur le périmètre : Bien que nous introduisions brièvement les W3C VC et SD-JWT pour fournir un contexte plus large, ce tutoriel met en œuvre exclusivement les identifiants ISO mDoc via OpenID4VP. Les VCs basés sur le W3C sont hors du champ de cet exemple.
La norme ISO/IEC 18013-5 mDoc définit la structure et l'encodage des documents mobiles tels que les permis de conduire mobiles (mDL). Les identifiants mDoc sont encodés en CBOR, signés cryptographiquement et peuvent être présentés numériquement pour vérification. Notre vérificateur se concentrera sur le décodage et la validation de ces identifiants mdoc.
OpenID4VP est un protocole interopérable pour demander et présenter des identifiants numériques, construit sur OAuth 2.0 et OpenID Connect. Dans cette implémentation, OpenID4VP est utilisé pour :
Maintenant que nous avons une compréhension claire des standards et des protocoles, nous devons choisir la bonne stack technique pour construire notre vérificateur. Nos choix sont conçus pour la robustesse, l'expérience de développement et la compatibilité avec l'écosystème web moderne.
Nous utiliserons TypeScript pour notre code frontend et backend. En tant que sur-ensemble de JavaScript, il ajoute un typage statique, ce qui aide à détecter les erreurs tôt, améliore la qualité du code et facilite la gestion d'applications complexes. Dans un contexte sensible à la sécurité comme la vérification d'identifiants, la sécurité des types est un avantage considérable.
Next.js est notre framework de choix car il offre une expérience intégrée et transparente pour la création d'applications full-stack.
redirect_uri
pour
recevoir et vérifier en toute sécurité la réponse finale du CMWallet.Notre implémentation repose sur un ensemble spécifique de bibliothèques pour le frontend et le backend :
Note sur openid-client
: Des vérificateurs plus avancés et prêts pour la
production pourraient utiliser la bibliothèque openid-client
pour gérer directement le
protocole OpenID4VP sur le backend, permettant des fonctionnalités comme un
redirect_uri
dynamique. Dans un flux OpenID4VP piloté par le serveur avec un
redirect_uri
, openid-client
serait utilisé pour analyser et valider directement les
réponses vp_token
. Pour ce tutoriel, nous utilisons un flux plus simple, médiatisé par
le navigateur, qui ne le nécessite pas, rendant le processus plus facile à comprendre.
Cette stack technique garantit une implémentation de vérificateur robuste, typée et évolutive, axée sur l'API Digital Credential du navigateur et le format d'identifiant ISO mDoc.
Pour tester votre vérificateur, vous avez besoin d'un portefeuille mobile capable d'interagir avec l'API Digital Credential du navigateur.
Nous utiliserons le CMWallet, un portefeuille de test robuste et conforme à OpenID4VP pour Android.
Comment installer CMWallet (Android) :
Note : N'installez des fichiers APK que de sources que vous jugez fiables. Le lien fourni provient du dépôt officiel du projet.
Avant de nous plonger dans l'implémentation, il est essentiel de comprendre les concepts cryptographiques qui sous-tendent les identifiants vérifiables. C'est ce qui les rend « vérifiables » et dignes de confiance.
Au fond, un identifiant vérifiable est un ensemble de déclarations (comme le nom, la date de naissance, etc.) qui a été signé numériquement par un émetteur. Une signature numérique offre deux garanties essentielles :
Les signatures numériques sont créées à l'aide de la cryptographie à clé publique/privée (également appelée cryptographie asymétrique). Voici comment cela fonctionne dans notre contexte :
Note sur les DID : Dans ce tutoriel, nous ne résolvons pas les clés de l'émetteur via les DID. En production, les émetteurs exposeraient généralement leurs clés publiques via des DID ou d'autres points de terminaison faisant autorité, que le vérificateur utiliserait pour la validation cryptographique.
Les identifiants vérifiables sont souvent formatés en tant que JSON Web Tokens (JWT).
Un JWT est un moyen compact et sûr pour les
URL de représenter des déclarations à transférer entre deux parties. Un
JWT signé (également connu sous le nom de
JWS) se compose de trois parties séparées par des points (.
) :
alg
).vc
), y
compris l'issuer
, le credentialSubject
, etc.// Exemple de la structure d'un JWT [En-tête].[Charge utile].[Signature]
Note : Les identifiants vérifiables basés sur JWT sont hors du champ de cet article de blog. Cette implémentation se concentre sur les identifiants ISO mDoc et OpenID4VP, et non sur les identifiants vérifiables W3C ou les identifiants basés sur JWT.
Il ne suffit pas qu'un vérificateur sache qu'un identifiant est valide ; il doit également savoir que la personne qui présente l'identifiant en est le titulaire légitime. Cela empêche quelqu'un d'utiliser un identifiant volé.
Ce problème est résolu à l'aide d'une présentation vérifiable (VP). Une VP est une enveloppe autour d'un ou plusieurs VCs qui est signée par le titulaire lui-même.
Le flux est le suivant :
Notre vérificateur doit alors effectuer deux vérifications de signature distinctes :
Cette double vérification garantit à la fois l'authenticité de l'identifiant et l'identité de la personne qui le présente, créant ainsi un modèle de confiance robuste et sécurisé.
Note : Le concept de
présentations vérifiables tel que
défini dans l'écosystème W3C VC est hors du champ de cet article de blog. Le terme
présentation vérifiable fait ici référence à la
réponse vp_token
d'OpenID4VP, qui se comporte de manière similaire à une VP du W3C
mais est basée sur la sémantique ISO mDoc plutôt que sur le modèle de signature
JSON-LD du W3C. Ce guide se concentre sur les identifiants ISO mDoc
et OpenID4VP, et non sur les
présentations vérifiables W3C ou la
validation de leur signature.
Notre architecture de vérificateur utilise l'API Digital Credential intégrée au navigateur comme intermédiaire sécurisé pour connecter notre application web au CMWallet mobile de l'utilisateur. Cette approche simplifie le flux en laissant le navigateur gérer l'affichage natif du QR code et la communication avec le portefeuille.
navigator.credentials.get()
du navigateur, de recevoir le résultat et de le
transmettre à notre backend pour vérification.openid4vp
et génère nativement un QR code. Il attend
ensuite que le portefeuille renvoie une réponse.Voici un diagramme de séquence illustrant le flux complet et précis :
Explication du flux :
/api/verify/start
), qui
génère un objet de requête contenant la requête (query) et un nonce, puis le renvoie.navigator.credentials.get()
avec l'objet de requête.openid4vp
et
affiche nativement un QR code. La promesse .get()
est maintenant en attente.Note : Ce flux de QR code se produit sur les navigateurs de bureau. Sur les
navigateurs mobiles (Android Chrome avec un flag expérimental activé), le navigateur
peut communiquer directement avec les portefeuilles
compatibles sur le même appareil, éliminant ainsi le besoin de scanner un QR code. Pour
activer cette fonctionnalité sur Android Chrome, accédez à
chrome://flags#web-identity-digital-credentials
et activez le flag.
.get()
initiale sur le frontend se résout enfin, livrant la charge utile de la
présentation./api/verify/finish
de notre backend. Le backend
valide le nonce et l'identifiant.Maintenant que nous avons une solide compréhension des standards, des protocoles et du flux architectural, nous pouvons commencer à construire notre vérificateur.
Suivez le guide ou utilisez le code final
Nous allons maintenant passer en revue la configuration et l'implémentation du code étape par étape. Si vous préférez passer directement au produit fini, vous pouvez cloner le projet complet depuis notre dépôt GitHub et l'exécuter localement.
git clone https://github.com/corbado/digital-credentials-example.git
Tout d'abord, nous allons initialiser un nouveau projet Next.js, installer les dépendances nécessaires et démarrer notre base de données.
Ouvrez votre terminal, naviguez vers le répertoire où vous souhaitez créer votre projet, et exécutez la commande suivante. Nous utilisons l'App Router, TypeScript et Tailwind CSS pour ce projet.
npx create-next-app@latest . --ts --eslint --tailwind --app --src-dir --import-alias "@/*" --use-npm
Cette commande crée un nouveau squelette d'application Next.js dans votre répertoire actuel.
Ensuite, nous devons installer les bibliothèques qui géreront le décodage CBOR, les connexions à la base de données et la génération d'UUID.
npm install cbor-web mysql2 uuid @types/uuid
Cette commande installe :
cbor-web
: Pour décoder la charge utile de l'identifiant mdoc.mysql2
: Le client MySQL pour notre base de données.uuid
: Pour générer des chaînes de challenge uniques.@types/uuid
: Les types TypeScript pour la bibliothèque uuid
.Notre backend nécessite une base de données MySQL pour stocker les données de session
OIDC, garantissant que chaque flux de vérification est sécurisé et avec état. Nous avons
inclus un fichier docker-compose.yml
pour faciliter cela.
Si vous avez cloné le dépôt, vous pouvez simplement exécuter docker-compose up -d
. Si
vous construisez à partir de zéro, créez un fichier nommé docker-compose.yml
avec le
contenu suivant :
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:
Cette configuration Docker Compose nécessite également un script d'initialisation SQL.
Créez un répertoire nommé sql
et à l'intérieur, un fichier nommé init.sql
avec le
contenu suivant pour configurer les tables nécessaires :
-- 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) );
Une fois que les deux fichiers sont en place, ouvrez votre terminal à la racine du projet et exécutez :
docker-compose up -d
Cette commande démarrera un conteneur MySQL en arrière-plan.
Notre application Next.js est structurée pour séparer les responsabilités entre le frontend et le backend, bien qu'ils fassent partie du même projet.
src/app/page.tsx
) : Une seule page React qui
lance le flux de vérification et affiche le résultat. Elle interagit avec l'API Digital
Credential du navigateur.src/app/api/verify/...
) :
start/route.ts
: Génère la requête OpenID4VP et un nonce de sécurité.finish/route.ts
: Reçoit la présentation du portefeuille (via le navigateur),
valide le nonce et décode l'identifiant.src/lib/
) :
database.ts
: Gère toutes les interactions avec la base de données (création de
challenges, vérification des sessions).crypto.ts
: Gère le décodage de l'identifiant mDoc basé sur CBOR.Voici un diagramme illustrant l'architecture interne :
Notre frontend est volontairement léger. Sa principale responsabilité est d'agir comme le déclencheur côté utilisateur pour le flux de vérification et de communiquer à la fois avec notre backend et les capacités natives de gestion des identifiants du navigateur. Il ne contient aucune logique de protocole complexe ; tout est délégué.
Plus précisément, le frontend gérera les éléments suivants :
/api/verify/start
et reçoit une
charge utile JSON structurée (protocol
, request
, state
) décrivant exactement ce
que le portefeuille doit présenter.navigator.credentials.get()
, qui affiche un QR code natif et attend la réponse du
portefeuille./api/verify/finish
dans une requête POST pour la validation finale côté serveur.La logique principale se trouve dans la fonction startVerification
:
// src/app/page.tsx const startVerification = async () => { setLoading(true); setVerificationResult(null); try { // 1. Vérifier si le navigateur supporte l'API if (!navigator.credentials?.get) { throw new Error("Le navigateur ne supporte pas l'API Credential."); } // 2. Demander à notre backend un objet de requête const res = await fetch("/api/verify/start"); const { protocol, request } = await res.json(); // 3. Transmettre cet objet au navigateur – cela déclenche le QR code natif const credential = await (navigator.credentials as any).get({ mediation: "required", digital: { requests: [ { protocol, // "openid4vp" data: request, // contient dcql_query, nonce, etc. }, ], }, }); // 4. Transférer la réponse du portefeuille (du navigateur) à notre point de terminaison finish pour les vérifications côté serveur 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(`Succès : ${result.message}`); } else { throw new Error(result.message || "La vérification a échoué."); } } catch (err) { setVerificationResult(`Erreur : ${(err as Error).message}`); } finally { setLoading(false); } };
Cette fonction montre les quatre étapes clés de la logique du frontend : vérifier la prise en charge de l'API, récupérer la requête du backend, appeler l'API du navigateur et renvoyer le résultat pour vérification. Le reste du fichier est du code standard React pour la gestion de l'état et le rendu de l'interface utilisateur, que vous pouvez consulter dans le dépôt GitHub.
digital
et mediation: 'required'
?#Vous remarquerez peut-être que notre appel à navigator.credentials.get()
est différent
d'exemples plus simples. C'est parce que nous nous conformons strictement à la
spécification officielle de l'API W3C Digital Credentials.
Membre digital
: La spécification exige que toutes les requêtes d'identifiants
numériques soient imbriquées dans un objet digital
. Cela fournit un espace de noms
clair et standardisé pour cette API, la distinguant des autres types d'identifiants
(comme password
ou federated
) et permettant des extensions futures sans conflits.
mediation: 'required'
: Cette option est une fonctionnalité cruciale de sécurité
et d'expérience utilisateur. Elle impose que l'utilisateur interagisse activement avec
une invite (par exemple, un scan biométrique, la saisie d'un PIN ou un écran de
consentement) pour approuver la demande d'identifiant. Sans cela, un site web pourrait
potentiellement tenter d'accéder silencieusement aux identifiants en arrière-plan, ce
qui représente un risque important pour la vie privée. En exigeant la médiation, nous
nous assurons que l'utilisateur a toujours le contrôle et donne son consentement
explicite pour chaque transaction.
Avec l'interface utilisateur React en place, nous avons maintenant besoin de deux routes API qui font le gros du travail sur le serveur :
/api/verify/start
– construit une requête OpenID4VP, persiste un challenge à
usage unique dans MySQL et renvoie le tout au navigateur./api/verify/finish
– reçoit la réponse du portefeuille, valide le challenge,
vérifie et décode l'identifiant, et renvoie finalement un résultat JSON concis à
l'interface utilisateur./api/verify/start
: Générer la requête 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️⃣ Créer un nonce (challenge) aléatoire et à courte durée de vie 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️⃣ Construire une requête DCQL qui décrit *ce que* nous voulons 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️⃣ Renvoyer un objet que le navigateur peut passer à navigator.credentials.get() return NextResponse.json({ protocol: "openid4vp", // indique au navigateur quel protocole de portefeuille utiliser request: { dcql_query: dcqlQuery, // CE QU'IL FAUT présenter nonce: challenge, // anti-replay response_type: "vp_token", response_mode: "dc_api", // le portefeuille enverra un POST directement à /finish }, state: { credential_type: "mso_mdoc", // conservé pour des vérifications ultérieures nonce: challenge, challenge_id: challengeId, }, }); }
Paramètres clés
• nonce
– challenge cryptographique qui lie
la requête et la réponse (empêche le rejeu). • dcql_query
– Un objet décrivant les
déclarations exactes dont nous avons besoin. Pour ce guide, nous utilisons une structure
dcql_query
inspirée des récents brouillons du Digital Credential Query Language, bien
que ce ne soit pas encore un standard finalisé. • state
– JSON arbitraire renvoyé
en écho par le portefeuille pour que nous puissions retrouver l'enregistrement dans la
BDD.
Le fichier src/lib/database.ts
encapsule les opérations MySQL de base pour les
challenges et les sessions de vérification (insérer, lire, marquer comme utilisé). Garder
cette logique dans un seul module facilite le remplacement du magasin de données plus
tard.
/api/verify/finish
: Valider et décoder la présentation#// 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️⃣ Extraire les éléments de la présentation vérifiable const vpTokenMap = body.vp_token ?? body.data?.vp_token; const state = body.state; const mdocToken = vpTokenMap?.cred1; // nous avons demandé cet ID dans dcqlQuery if (!vpTokenMap || !state || !mdocToken) { return NextResponse.json( { verified: false, message: "Réponse mal formée" }, { status: 400 }, ); } // 2️⃣ Validation du challenge à usage unique const stored = await getChallenge(state.nonce); if (!stored) { return NextResponse.json( { verified: false, message: "Challenge invalide ou expiré" }, { status: 400 }, ); } const sessionId = uuidv4(); await createVerificationSession(sessionId, stored.id); // 3️⃣ (Pseudo) vérifications cryptographiques – à remplacer par une vraie validation mDL en prod // Dans une application réelle, vous utiliseriez une bibliothèque dédiée pour effectuer une validation // cryptographique complète de la signature mdoc par rapport à la clé publique de l'émetteur. const isValid = mdocToken.length > 0; if (!isValid) { await updateVerificationSession(sessionId, "failed", { reason: "la validation mdoc a échoué", }); return NextResponse.json( { verified: false, message: "La validation de l'identifiant a échoué" }, { status: 400 }, ); } // 4️⃣ Décoder la charge utile du mobile-DL (mdoc) en JSON lisible par l'homme 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: "Identifiant mdoc vérifié avec succès !", credentialData: readable, sessionId, }); }
Champs importants dans la réponse du portefeuille
• vp_token
– une map qui contient chaque identifiant renvoyé par le
portefeuille. Pour notre démo, nous extrayons vp_token.cred1
. • state
– l'écho
du blob que nous avons fourni dans /start
; contient le nonce
pour que nous
puissions retrouver l'enregistrement dans la BDD. • mdocToken
– une structure CBOR
encodée en Base64URL qui représente l'ISO mDoc.
Lorsque le vérificateur reçoit un identifiant mdoc du navigateur, il s'agit d'une chaîne
Base64URL contenant des données binaires encodées en CBOR. Pour extraire les déclarations
réelles, le point de terminaison finish
effectue un processus de décodage en plusieurs
étapes à l'aide de fonctions d'assistance de src/lib/crypto.ts
.
La fonction decodeDigitalCredential
gère la conversion de la chaîne encodée en un objet
utilisable :
// src/lib/crypto.ts export async function decodeDigitalCredential(encodedCredential: string) { // 1. Convertir Base64URL en 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. Décoder Base64 en binaire const binaryString = atob(base64); const byteArray = Uint8Array.from(binaryString, (char) => char.charCodeAt(0)); // 3. Décoder CBOR const decoded = await cbor.decodeFirst(byteArray); return decoded; }
cbor-web
pour décoder les données binaires
en un objet JavaScript structuré.La fonction decodeAllNamespaces
traite ensuite l'objet CBOR décodé pour extraire les
déclarations réelles des espaces de noms pertinents :
// 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 (si présent): 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
) pour extraire
les valeurs réelles des déclarations (telles que le nom, la date de naissance, etc.).Après avoir suivi ces étapes, le point de terminaison finish obtient un objet lisible par l'homme contenant les déclarations du mdoc, par exemple :
{ "family_name": "Doe", "given_name": "John", "birth_date": "1990-01-01" }
Ce processus garantit que le vérificateur peut extraire de manière sécurisée et fiable les informations nécessaires de l'identifiant mdoc pour l'affichage et le traitement ultérieur.
Le point de terminaison finish renvoie un objet JSON minimal au frontend :
{ "verified": true, "message": "Identifiant mdoc vérifié avec succès !", "credentialData": { "family_name": "Doe", "given_name": "John", "birth_date": "1990-01-01" } }
Le frontend reçoit cette réponse dans startVerification()
et la persiste simplement dans
l'état React pour que nous puissions afficher une belle carte de confirmation ou afficher
des déclarations individuelles – par exemple « Bienvenue, John Doe (né le 1990-01-01) !
».
Vous disposez maintenant d'un vérificateur complet et fonctionnel qui utilise les capacités natives de gestion des identifiants du navigateur. Voici comment l'exécuter localement et ce que vous pouvez faire pour le faire passer d'une preuve de concept à une application prête pour la production.
Cloner le dépôt :
git clone https://github.com/corbado/digital-credentials-example.git cd digital-credentials-example
Installer les dépendances :
npm install
Démarrer la base de données : Assurez-vous que Docker est en cours d'exécution sur votre machine, puis démarrez le conteneur MySQL :
docker-compose up -d
Lancer l'application :
npm run dev
Ouvrez votre navigateur à l'adresse http://localhost:3000
, et vous devriez voir
l'interface utilisateur du vérificateur. Vous pouvez maintenant utiliser votre
CMWallet pour scanner le QR code et compléter le flux de vérification.
Ce tutoriel fournit les briques de base pour un vérificateur. Pour le rendre prêt pour la production, vous devriez implémenter plusieurs fonctionnalités supplémentaires :
Validation cryptographique complète : L'implémentation actuelle utilise une
vérification de substitution (mdocToken.length > 0
). Dans un scénario réel, vous devez
effectuer une validation cryptographique complète de la signature mdoc par rapport à la
clé publique de l'émetteur (par exemple, en résolvant son DID ou en
récupérant son certificat de clé publique). Pour les standards de résolution de DID,
consultez la spécification W3C DID Resolution.
Vérification de la révocation par l'émetteur : Les identifiants peuvent être révoqués par l'émetteur avant leur date d'expiration. Un vérificateur de production doit vérifier le statut de l'identifiant en interrogeant une liste de révocation ou un point de terminaison de statut fourni par l'émetteur. La W3C Verifiable Credentials Status List fournit le standard pour les listes de révocation d'identifiants.
Gestion robuste des erreurs et sécurité : Ajoutez une gestion complète des erreurs, une validation des entrées, une limitation du débit sur les points de terminaison de l'API, et assurez-vous que toutes les communications se font via HTTPS (TLS) pour protéger les données en transit. Les OWASP API Security Guidelines fournissent des meilleures pratiques complètes pour la sécurité des API.
Prise en charge de plusieurs types d'identifiants : Étendez la logique pour gérer
différentes valeurs de doctype
et formats d'identifiants si vous prévoyez de recevoir
plus que le simple identifiant PID de
l'identité numérique européenne (EUDI).
Le W3C Verifiable Credentials Data Model fournit
des spécifications complètes sur les formats de VC.
Cet exemple est volontairement axé sur le flux principal médiatisé par le navigateur pour le rendre facile à comprendre. Les sujets suivants sont considérés comme hors du champ d'application :
redirect_uri
ou l'enregistrement de client dynamique.En vous basant sur cette fondation et en intégrant ces prochaines étapes, vous pouvez développer un vérificateur robuste et sécurisé capable de faire confiance et de valider les identifiants numériques dans vos propres applications.
C'est tout ! Avec moins de 250 lignes de TypeScript, nous avons maintenant un vérificateur de bout en bout qui :
En production, vous remplaceriez la validation de substitution par des vérifications complètes ISO 18013-5, ajouteriez des consultations de listes de révocation d'émetteurs, une limitation de débit, des journaux d'audit et, bien sûr, le TLS de bout en bout, mais les briques de base restent exactement les mêmes.
Voici quelques-unes des principales ressources, spécifications et outils utilisés ou référencés dans ce tutoriel :
Dépôt du projet :
Spécifications clés :
Outils :
Bibliothèques :
Related Articles
Table of Contents