Get your free and exclusive 80-page Banking Passkey Report
Back to Overview

Comment construire un vérificateur d'identifiants numériques (Guide du développeur)

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

Blog-Post-Header-Image

See the original blog version in English here.

DigitalCredentialsDemo Icon

Want to experience digital credentials in action?

Try Digital Credentials

1. Introduction#

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é.

1.1 Comment ça marche#

La magie des identifiants numériques repose sur un modèle simple mais puissant de « triangle de confiance » impliquant trois acteurs clés :

  • Émetteur : Une autorité de confiance (par exemple, une agence gouvernementale, une université ou une banque) qui signe cryptographiquement et émet un identifiant pour un utilisateur.
  • Titulaire : L'utilisateur, qui reçoit l'identifiant et le stocke en toute sécurité dans un portefeuille numérique personnel sur son appareil.
  • Vérificateur : Une application ou un service qui doit contrôler l'identifiant de l'utilisateur.

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.

1.2 Pourquoi les vérificateurs sont essentiels (et pourquoi nous sommes ici)#

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.

2. Prérequis pour construire un vérificateur#

Avant de commencer, assurez-vous d'avoir :

  1. Compréhension de base des identifiants numériques et du mdoc
    • Ce tutoriel se concentre sur le format ISO mDoc (par exemple, pour les permis de conduire mobiles) et ne couvre pas d'autres formats comme les identifiants vérifiables (VCs) du W3C. Une familiarité avec les concepts de base du mdoc sera utile.
  2. Docker et Docker Compose
    • Notre projet utilise une base de données MySQL dans un conteneur Docker pour gérer l'état de la session OIDC. Assurez-vous que les deux sont installés et en cours d'exécution.
  3. Protocole choisi : OpenID4VP
    • Nous utiliserons le protocole OpenID4VP (OpenID for Verifiable Presentations) pour le flux d'échange d'identifiants.
  4. Stack technique prête
    • Utilisation de TypeScript (Node.js) pour la logique backend.
    • Utilisation de Next.js pour le backend (API routes) et le frontend (UI).
    • Bibliothèques clés : des bibliothèques de décodage CBOR pour l'analyse du mdoc et un client MySQL.
  5. Identifiants de test et portefeuille
    • Nous utiliserons le CMWallet pour Android, qui comprend les requêtes OpenID4VP et peut présenter des identifiants mdoc.
  6. Connaissances de base en cryptographie
    • Comprendre les signatures numériques et les concepts de clé publique/privée tels qu'ils s'appliquent au mdoc et aux flux OIDC.

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.

2.1 Choix des protocoles#

Notre vérificateur est construit pour les éléments suivants :

Standard / ProtocoleDescription
W3C VCLe 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-JWTDivulgation 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 mDocISO/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.
OpenID4VPOpenID 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 :

  • OpenID4VP comme protocole pour demander et recevoir les identifiants.
  • ISO mDoc comme format d'identifiant (par exemple, pour les permis de conduire mobiles).

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.

2.1.1 ISO mDoc (Mobile Document)#

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.

2.1.2 OpenID4VP (OpenID for Verifiable Presentations)#

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 :

  • Lancer le flux de présentation de l'identifiant (via un QR code ou une API de navigateur)
  • Recevoir l'identifiant mdoc du portefeuille de l'utilisateur
  • Assurer un échange d'identifiants sécurisé, avec état et respectueux de la vie privée

2.2 Choix de la stack technique#

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.

2.2.1 Langage : TypeScript#

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.

2.2.2 Framework : Next.js#

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.

  • Pour le Frontend : Nous utiliserons Next.js avec React pour construire l'interface utilisateur où le processus de vérification est initié (par exemple, en affichant un QR code).
  • Pour le Backend : Nous tirerons parti des API Routes de Next.js pour créer les points de terminaison côté serveur. Ces points de terminaison sont responsables de la création de requêtes OpenID4VP valides et d'agir en tant que redirect_uri pour recevoir et vérifier en toute sécurité la réponse finale du CMWallet.

2.2.3 Bibliothèques clés#

Notre implémentation repose sur un ensemble spécifique de bibliothèques pour le frontend et le backend :

  • next : Le framework Next.js, utilisé à la fois pour les routes API du backend et l'interface utilisateur du frontend.
  • react et react-dom : Alimentent l'interface utilisateur du frontend.
  • cbor-web : Utilisé pour décoder les identifiants mdoc encodés en CBOR en objets JavaScript utilisables.
  • mysql2 : Fournit la connectivité à la base de données MySQL pour stocker les challenges et les sessions de vérification.
  • uuid : Une bibliothèque pour générer des chaînes de challenge uniques (nonces).
  • @types/uuid : Types TypeScript pour la génération d'UUID.

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.

2.3 Obtenir un portefeuille et des identifiants de test#

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) :

  1. Téléchargez le fichier APK en utilisant le lien ci-dessus directement sur votre appareil Android.
  2. Ouvrez les Paramètres > Sécurité de votre appareil.
  3. Activez « Installer des applications inconnues » pour le navigateur que vous avez utilisé pour télécharger le fichier.
  4. Localisez l'APK téléchargé dans votre dossier « Téléchargements » et appuyez dessus pour commencer l'installation.
  5. Suivez les instructions à l'écran pour terminer l'installation.
  6. Ouvrez CMWallet, et vous le trouverez pré-chargé avec des identifiants de test, prêt pour le flux de vérification.

Note : N'installez des fichiers APK que de sources que vous jugez fiables. Le lien fourni provient du dépôt officiel du projet.

2.4 Connaissances en cryptographie#

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.

2.4.1 Les signatures numériques : le fondement de la 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 :

  • Authenticité : Elle prouve que l'identifiant a bien été créé par l'émetteur et non par un imposteur.
  • Intégrité : Elle prouve que l'identifiant n'a pas été modifié ou altéré depuis sa signature.

2.4.2 Cryptographie à clé publique/privée#

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 :

  1. L'émetteur possède une paire de clés : une clé privée, qui est gardée secrète, et une clé publique, qui est mise à la disposition de tous (généralement via son document DID).
  2. Signature : Lorsqu'un émetteur crée un identifiant, il utilise sa clé privée pour générer une signature numérique unique pour les données spécifiques de cet identifiant.
  3. Vérification : Lorsque notre vérificateur reçoit l'identifiant, il utilise la clé publique de l'émetteur pour vérifier la signature. Si la vérification réussit, le vérificateur sait que l'identifiant est authentique et n'a pas été altéré. Toute modification des données de l'identifiant invaliderait la signature.

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.

2.4.3 Les identifiants vérifiables en tant que JWT#

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 (.) :

  • En-tête : Contient des métadonnées sur le token, comme l'algorithme de signature utilisé (alg).
  • Charge utile (Payload) : Contient les déclarations réelles de l'identifiant vérifiable (la déclaration vc), y compris l'issuer, le credentialSubject, etc.
  • Signature : La signature numérique générée par l'émetteur, qui couvre l'en-tête et la charge utile.
// 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.

2.4.4 La présentation vérifiable : prouver la possession#

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 :

  1. Le vérificateur demande à l'utilisateur de présenter un identifiant.
  2. Le portefeuille de l'utilisateur crée une présentation vérifiable, y regroupe le(s) identifiant(s) requis, et signe l'ensemble de la présentation en utilisant la clé privée du titulaire.
  3. Le portefeuille envoie cette VP signée au vérificateur.

Notre vérificateur doit alors effectuer deux vérifications de signature distinctes :

  1. Vérifier le(s) identifiant(s) : Vérifier la signature de chaque VC à l'intérieur de la présentation en utilisant la clé publique de l'émetteur. (Prouve que l'identifiant est authentique).
  2. Vérifier la présentation : Vérifier la signature de la VP elle-même en utilisant la clé publique du titulaire. (Prouve que la personne qui la présente en est le propriétaire).

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.

3. Vue d'ensemble de l'architecture#

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.

  • Frontend (Next.js & React) : Un site web léger destiné à l'utilisateur. Son rôle est de récupérer un objet de requête de notre backend, de le transmettre à l'API navigator.credentials.get() du navigateur, de recevoir le résultat et de le transmettre à notre backend pour vérification.
  • Backend (API Routes de Next.js) : Le cœur du vérificateur. Il génère un objet de requête valide pour l'API du navigateur et expose un point de terminaison pour recevoir la présentation de l'identifiant depuis le frontend pour la validation finale.
  • Navigateur (Credential API) : Le facilitateur. Il reçoit l'objet de requête de notre frontend, comprend le protocole openid4vp et génère nativement un QR code. Il attend ensuite que le portefeuille renvoie une réponse.
  • CMWallet (Application mobile) : Le portefeuille de l'utilisateur. Il scanne le QR code, traite la requête, obtient le consentement de l'utilisateur et renvoie la réponse signée au navigateur.

Voici un diagramme de séquence illustrant le flux complet et précis :

Explication du flux :

  1. Initiation : L'utilisateur clique sur le bouton « Vérifier » sur notre Frontend.
  2. Objet de requête : Le frontend appelle notre Backend (/api/verify/start), qui génère un objet de requête contenant la requête (query) et un nonce, puis le renvoie.
  3. Appel à l'API du navigateur : Le frontend appelle navigator.credentials.get() avec l'objet de requête.
  4. QR code natif : Le Navigateur voit la requête de protocole 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.

  1. Scan et présentation : L'utilisateur scanne le QR code avec le CMWallet. Le portefeuille obtient l'approbation de l'utilisateur et renvoie la présentation vérifiable au navigateur.
  2. Résolution de la promesse : Le navigateur reçoit la réponse, et la promesse .get() initiale sur le frontend se résout enfin, livrant la charge utile de la présentation.
  3. Vérification par le backend : Le frontend envoie par POST la charge utile de la présentation au point de terminaison /api/verify/finish de notre backend. Le backend valide le nonce et l'identifiant.
  4. Résultat : Le backend renvoie un message final de succès ou d'échec au frontend, qui met à jour l'interface utilisateur.

4. Construire le vérificateur#

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

4.1 Mise en place du projet#

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.

4.1.1 Initialisation de l'application Next.js#

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.

4.1.2 Installation des dépendances#

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.

4.1.3 Démarrage de la base de données#

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.

4.2 Vue d'ensemble de l'architecture de l'application Next.js#

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.

  • Frontend (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.
  • Routes API du backend (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.
  • Bibliothèque (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 :

4.3 Construire le frontend#

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 :

  • Interaction utilisateur : Fournit une interface simple, comme un bouton « Vérifier », pour que l'utilisateur puisse démarrer le processus.
  • Gestion de l'état : Gère l'état de l'interface utilisateur, en affichant des indicateurs de chargement pendant la vérification et en affichant le message final de succès ou d'erreur.
  • Communication avec le backend (Requête) : Appelle /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.
  • Invocation de l'API du navigateur : Transmet cet objet JSON à navigator.credentials.get(), qui affiche un QR code natif et attend la réponse du portefeuille.
  • Communication avec le backend (Réponse) : Une fois que l'API du navigateur renvoie la présentation vérifiable, elle envoie ces données à notre point de terminaison /api/verify/finish dans une requête POST pour la validation finale côté serveur.
  • Affichage des résultats : Met à jour l'interface utilisateur pour informer l'utilisateur si la vérification a réussi ou échoué, en fonction de la réponse du backend.

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.

Pourquoi 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.

4.4 Construire les points de terminaison du backend#

Avec l'interface utilisateur React en place, nous avons maintenant besoin de deux routes API qui font le gros du travail sur le serveur :

  1. /api/verify/start – construit une requête OpenID4VP, persiste un challenge à usage unique dans MySQL et renvoie le tout au navigateur.
  2. /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.

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

noncechallenge 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.

4.4.2 Assistants de base de données#

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.


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

4.6 Décodage de l'identifiant 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.

4.6.1 Étape 1 : Décodage Base64URL et CBOR#

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; }
  • Base64URL en Base64 : Convertit l'identifiant de l'encodage Base64URL à l'encodage Base64 standard.
  • Base64 en Binaire : Décode la chaîne Base64 en un tableau d'octets binaires.
  • Décodage CBOR : Utilise la bibliothèque cbor-web pour décoder les données binaires en un objet JavaScript structuré.

4.6.2 Étape 2 : Extraction des déclarations par espace de noms#

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; }
  • Itère sur tous les documents dans l'identifiant décodé.
  • Décode chaque espace de noms (par exemple, eu.europa.ec.eudi.pid.1) pour extraire les valeurs réelles des déclarations (telles que le nom, la date de naissance, etc.).
  • Gère les espaces de noms signés par l'émetteur et par l'appareil s'ils sont présents.

Exemple de sortie#

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.

4.7 Afficher le résultat dans l'interface utilisateur#

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) ! ».

5. Exécuter le vérificateur et prochaines étapes#

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.

5.1 Comment exécuter l'exemple#

  1. Cloner le dépôt :

    git clone https://github.com/corbado/digital-credentials-example.git cd digital-credentials-example
  2. Installer les dépendances :

    npm install
  3. 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
  4. 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.

5.2 Prochaines étapes : de la démo à la production#

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.

5.3 Ce qui est hors du champ de ce tutoriel#

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 :

  • Sécurité prête pour la production : Le vérificateur est à des fins éducatives et ne dispose pas du renforcement requis pour un environnement en direct.
  • Identifiants vérifiables W3C : Ce tutoriel se concentre exclusivement sur le format ISO mDoc pour les permis de conduire mobiles. Il ne couvre pas d'autres formats populaires comme les JWT-VC ou les VC avec des preuves de données liées (LD-Proofs).
  • Flux OpenID4VP avancés : Nous n'implémentons pas de fonctionnalités OpenID4VP plus complexes, telles que la communication directe entre le portefeuille et le backend à l'aide d'un 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.

Conclusion#

C'est tout ! Avec moins de 250 lignes de TypeScript, nous avons maintenant un vérificateur de bout en bout qui :

  1. Publie une requête pour l'API d'identifiants du navigateur.
  2. Permet à tout portefeuille compatible de fournir une présentation vérifiable.
  3. Valide la présentation sur le serveur.
  4. Met à jour l'interface utilisateur en temps réel.

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.

Ressources#

Voici quelques-unes des principales ressources, spécifications et outils utilisés ou référencés dans ce tutoriel :

Add passkeys to your app in <1 hour with our UI components, SDKs & guides.

Start Free Trial

Share this article


LinkedInTwitterFacebook

Table of Contents