---
url: 'https://www.corbado.com/fr/blog/comment-creer-emetteur-verifiable-credential'
title: 'Comment créer un émetteur d''identifiants numériques (Guide du développeur)'
description: 'Apprenez à construire un émetteur de Verifiable Credentials W3C en utilisant le protocole OpenID4VCI. Ce guide étape par étape vous montre comment créer une application Next.js qui émet des identifiants signés cryptographiquement, compatibles avec les wal'
lang: 'fr'
author: 'Amine'
date: '2025-08-20T15:39:17.414Z'
lastModified: '2026-03-27T07:05:50.699Z'
keywords: 'émetteur d''identifiants numériques, tutoriel émetteur, créer émetteur'
category: 'Digital Credentials'
---

# Comment créer un émetteur d'identifiants numériques (Guide du développeur)

## 1. Introduction

Les [identifiants numériques](https://www.corbado.com/fr/blog/digital-credentials-api) sont un moyen puissant de
prouver une identité et des attestations de manière sécurisée et respectueuse de la vie
privée. Mais comment les utilisateurs obtiennent-ils ces identifiants ? C'est là que le
rôle de l'**Issuer** (émetteur) devient crucial. Un [Issuer](https://www.corbado.com/glossary/issuer) est une
entité de confiance, comme une agence [gouvernementale](https://www.corbado.com/passkeys-for-public-sector), une
université ou une banque, chargée de créer et de distribuer des identifiants signés
numériquement aux utilisateurs.

Ce guide propose un [tutoriel](https://www.corbado.com/fr/blog/application-crud-react-nodejs-express-mysql)
complet, étape par étape, pour construire un [Issuer](https://www.corbado.com/glossary/issuer)
d'[identifiants numériques](https://www.corbado.com/fr/blog/digital-credentials-api). Nous nous concentrerons sur
le protocole **OpenID for Verifiable Credential Issuance (OpenID4VCI)**, une norme moderne
qui définit comment les utilisateurs peuvent obtenir des identifiants auprès d'un
[Issuer](https://www.corbado.com/glossary/issuer) et les stocker en toute sécurité dans leurs wallets numériques.

Le résultat final sera une application [Next.js](https://www.corbado.com/blog/nextjs-passkeys) fonctionnelle
capable de :

1. Accepter les données de l'utilisateur via un simple formulaire web.
2. Générer une offre d'identifiant sécurisée et à usage unique.
3. Afficher l'offre sous forme de [QR code](https://www.corbado.com/blog/qr-code-login-authentication) que
   l'utilisateur peut scanner avec son [wallet](https://www.corbado.com/blog/digital-wallet-assurance) mobile.
4. Émettre un identifiant signé cryptographiquement que l'utilisateur peut stocker et
   présenter pour vérification.

### 1.1 Comprendre la terminologie : Identifiants numériques vs. Verifiable Credentials

Avant de continuer, il est important de clarifier la distinction entre deux concepts liés
mais différents :

- **Identifiants numériques (terme général) :** Il s'agit d'une vaste catégorie qui
  englobe toute forme numérique d'identifiants, de certificats ou d'attestations. Cela
  peut inclure de simples certificats numériques, des badges numériques de base, ou tout
  identifiant stocké électroniquement qui peut ou non avoir des fonctionnalités de
  sécurité cryptographique.

- **Verifiable Credentials (VC - Standard W3C) :** Il s'agit d'un type spécifique
  d'identifiant numérique qui suit la norme W3C
  [Verifiable Credentials](https://www.corbado.com/glossary/microcredentials) Data Model. Les
  [Verifiable Credentials](https://www.corbado.com/glossary/microcredentials) sont des identifiants signés
  cryptographiquement, infalsifiables et respectueux de la vie privée, qui peuvent être
  vérifiés de manière indépendante. Ils incluent des exigences techniques spécifiques
  comme :
    - Des signatures cryptographiques pour l'authenticité et l'intégrité
    - Un modèle de données et des formats standardisés
    - Des mécanismes de présentation respectueux de la vie privée
    - Des protocoles de vérification interopérables

**Dans ce guide, nous construisons spécifiquement un émetteur de Verifiable Credentials**
qui suit la norme W3C, et non un simple système
d'[identifiants numériques](https://www.corbado.com/fr/blog/digital-credentials-api). Le protocole
[OpenID4VCI](https://www.corbado.com/glossary/openid4vci) que nous utilisons est conçu spécifiquement pour
l'émission de Verifiable Credentials, et le format [JWT](https://www.corbado.com/fr/glossary/jwks)-VC que nous
mettrons en œuvre est un format conforme au W3C pour les
[Verifiable Credentials](https://www.corbado.com/glossary/microcredentials).

### 1.2 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 :

- **Issuer (Émetteur) :** Une autorité de confiance (par exemple, une agence
  [gouvernementale](https://www.corbado.com/passkeys-for-public-sector), une université ou une banque) qui signe
  cryptographiquement et émet un identifiant à un utilisateur. **C'est le rôle que nous
  construisons dans ce guide.**
- **Holder (Détenteur) :** L'utilisateur, qui reçoit l'identifiant et le stocke en toute
  sécurité dans un [wallet](https://www.corbado.com/blog/digital-wallet-assurance) numérique personnel sur son
  appareil.
- **Verifier (Vérificateur) :** Une application ou un service qui doit vérifier
  l'identifiant de l'utilisateur.

![Écosystème des Verifiable Credentials W3C](https://www.w3.org/TR/vc-data-model/diagrams/ecosystem.svg)

Le flux d'émission est la première étape de cet écosystème. L'Issuer valide les
informations de l'utilisateur et lui fournit un identifiant. Une fois que le Holder a cet
identifiant dans son [wallet](https://www.corbado.com/blog/digital-wallet-assurance), il peut le présenter à un
Verifier pour prouver son identité ou ses attestations, complétant ainsi le triangle.

Voici un aperçu rapide de l'application finale en action :

**Étape 1 : Saisie des données de l'utilisateur** L'utilisateur remplit un formulaire avec
ses informations personnelles pour demander un nouvel identifiant.
![Formulaire de saisie des données de l'utilisateur](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/issuer_step_1_0733a9e1da.png)

**Étape 2 : Génération de l'offre d'identifiant** L'application génère une offre
d'identifiant sécurisée, affichée sous forme de
[QR code](https://www.corbado.com/blog/qr-code-login-authentication) et d'un code pré-autorisé.
![QR code de l'offre d'identifiant](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/issuer_step_2_3f1881c473.png)

**Étape 3 : Interaction avec le wallet** L'utilisateur scanne le
[QR code](https://www.corbado.com/blog/qr-code-login-authentication) avec un wallet compatible (par exemple,
Sphereon Wallet) et saisit un code PIN pour autoriser l'émission.
![Offre d'identifiant sur le wallet](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/issuer_step_3_b80d689dfe.png)
![Saisie du code PIN](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/issuer_step_4_ca8bad8d11.png)

**Étape 4 : Identifiant émis** Le wallet reçoit et stocke le nouvel identifiant numérique
émis, prêt pour une utilisation future.
![Confirmation des détails de l'identifiant](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/issuer_step_5_55b8150597.png)
![Identifiant ajouté](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/issuer_step_6_7f5ac5745d.png)

## 2. Prérequis pour construire un Issuer

Avant de plonger dans le code, passons en revue les connaissances fondamentales et les
outils dont nous aurons besoin. Ce guide suppose une familiarité de base avec les concepts
de développement web, mais les prérequis suivants sont essentiels pour construire un
émetteur d'identifiants.

### 2.1 Choix des protocoles

Notre Issuer est basé sur un ensemble de normes ouvertes qui garantissent
l'interopérabilité entre les wallets et les services d'émission. Pour ce
[tutoriel](https://www.corbado.com/fr/blog/application-crud-react-nodejs-express-mysql), nous nous concentrerons
sur les éléments suivants :

| Norme / Protocole                                                 | Description                                                                                                                                                                                                                                                           |
| :---------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **OpenID4VCI**                                                    | **OpenID for Verifiable Credential Issuance.** C'est le protocole principal que nous utiliserons. Il définit un flux standard pour la manière dont un utilisateur (via son wallet) peut demander et recevoir un identifiant d'un Issuer.                              |
| **[JWT-VC](https://www.w3.org/TR/vc-data-model/#json-web-token)** | **Verifiable Credentials basés sur JWT.** Le format de l'identifiant que nous émettrons. C'est une norme W3C qui encode les Verifiable Credentials en tant que JSON Web Tokens (JWT), les rendant compacts et adaptés au web.                                         |
| **[ISO mDoc](https://www.iso.org/standard/69084.html)**           | **ISO/IEC 18013-5.** La norme internationale pour les permis de conduire mobiles (mDL). Bien que nous émettions un JWT-VC, les _claims_ qu'il contient sont structurés pour être compatibles avec le modèle de données mDoc (par exemple, `eu.europa.ec.eudi.pid.1`). |
| **OAuth 2.0**                                                     | Le framework d'autorisation sous-jacent utilisé par OpenID4VCI. Nous mettrons en œuvre un flux `pre-authorized_code`, qui est un type de subvention spécifique conçu pour une émission d'identifiants sécurisée et conviviale.                                        |

#### 2.1.1 Flux d'autorisation : Code pré-autorisé vs. Code d'autorisation

[OpenID4VCI](https://www.corbado.com/glossary/openid4vci) prend en charge deux flux d'autorisation principaux
pour l'émission d'identifiants :

1. **Flux de code pré-autorisé :** Dans ce flux, l'Issuer génère un code à usage unique et
   de courte durée (`pre-authorized_code`) qui est immédiatement disponible pour
   l'utilisateur. Le wallet de l'utilisateur peut ensuite échanger ce code directement
   contre un identifiant. Ce flux est idéal pour les scénarios où l'utilisateur est déjà
   authentifié et présent sur le site web de l'Issuer, car il offre une expérience
   d'émission instantanée et transparente, sans redirection.

2. **Flux de code d'autorisation :** Il s'agit du flux [OAuth 2.0](https://www.corbado.com/glossary/oauth2)
   standard, où l'utilisateur est redirigé vers un serveur d'autorisation pour donner son
   consentement. Après approbation, le serveur renvoie un `authorization_code` à un
   `redirect_uri` enregistré. Ce flux est plus adapté aux applications tierces qui lancent
   le processus d'émission au nom de l'utilisateur.

**Pour ce tutoriel, nous utiliserons le flux `pre-authorized_code`.** Nous avons choisi
cette approche car elle est plus simple et offre une expérience utilisateur plus directe
pour notre cas d'usage spécifique : un utilisateur demandant directement un identifiant
sur le site web de l'Issuer. Cela élimine le besoin de redirections complexes et
d'enregistrement de client, rendant la logique d'émission de base plus facile à comprendre
et à mettre en œuvre.

Cette combinaison de normes nous permet de construire un émetteur compatible avec un large
éventail de wallets numériques et garantit un processus sécurisé et standardisé pour
l'utilisateur.

### 2.2 Choix de la stack technique

Pour construire notre émetteur, nous utiliserons la même stack technique robuste et
moderne que celle utilisée pour le vérificateur, garantissant une expérience de
développement cohérente et de haute qualité.

#### 2.2.1 Langage : TypeScript

Nous utiliserons **TypeScript** pour notre code frontend et backend. Son typage statique
est inestimable dans une application critique pour la sécurité comme un émetteur, car il
aide à prévenir les erreurs courantes et améliore la qualité et la maintenabilité globales
du code.

#### 2.2.2 Framework : Next.js

**Next.js** est notre framework de prédilection car il offre une expérience intégrée et
transparente pour la création d'applications
[full-stack](https://www.corbado.com/fr/blog/application-crud-react-nodejs-express-mysql).

- **Pour le frontend :** Nous utiliserons [Next.js](https://www.corbado.com/blog/nextjs-passkeys) avec
  [React](https://www.corbado.com/blog/react-passkeys) pour construire l'interface utilisateur où les
  utilisateurs peuvent saisir leurs données pour demander un identifiant.
- **Pour le backend :** Nous tirerons parti des **Next.js API Routes** pour créer les
  points de terminaison côté serveur qui gèrent le flux
  [OpenID4VCI](https://www.corbado.com/glossary/openid4vci), de la génération des offres d'identifiants à
  l'émission de l'identifiant final signé.

#### 2.2.3 Bibliothèques clés

Notre implémentation s'appuiera sur quelques bibliothèques clés pour gérer des tâches
spécifiques :

- **next**, **react** et **react-dom** : Les bibliothèques de base pour notre application
  [Next.js](https://www.corbado.com/blog/nextjs-passkeys).
- **mysql2** : Un client [MySQL](https://www.corbado.com/blog/passkey-webauthn-database-guide) pour
  [Node.js](https://www.corbado.com/blog/nodejs-passkeys), utilisé pour stocker les codes d'autorisation et les
  données de session.
- **uuid** : Une bibliothèque pour générer des identifiants uniques, que nous utiliserons
  pour créer les valeurs `pre-authorized_code`.
- **jose** : Une bibliothèque robuste pour gérer les JSON Web Signatures (JWS), que nous
  utiliserons pour signer cryptographiquement les identifiants que nous émettons.

### 2.3 Obtenir un wallet de test

Pour tester votre émetteur, vous aurez besoin d'un wallet mobile qui prend en charge le
protocole OpenID4VCI. Pour ce
[tutoriel](https://www.corbado.com/fr/blog/application-crud-react-nodejs-express-mysql), nous recommandons le
**Sphereon Wallet**, qui est disponible pour
[Android](https://www.corbado.com/blog/how-to-enable-passkeys-android) et
[iOS](https://www.corbado.com/blog/how-to-enable-passkeys-ios).

**Comment installer Sphereon Wallet :**

1. **Téléchargez le wallet** depuis le
   [Google Play Store](https://play.google.com/store/apps/details?id=com.sphereon.ssi.wallet)
   ou l'[Apple App Store](https://apps.apple.com/us/app/sphereon-wallet/id1661096796).
2. Installez l'application sur votre appareil mobile.
3. Une fois installé, le wallet est prêt à recevoir des offres d'identifiants en scannant
   un QR code.

### 2.4 Connaissances en cryptographie

L'émission d'un identifiant est une opération critique pour la sécurité qui repose sur des
concepts cryptographiques fondamentaux pour garantir la confiance et l'authenticité.

#### 2.4.1 Signatures numériques

Au cœur, un [Verifiable Credential](https://www.corbado.com/glossary/verifiable-credential) est un ensemble
d'attestations qui a été **signé numériquement** par l'Issuer. Cette signature offre deux
garanties :

- **Authenticité :** Elle prouve que l'identifiant a été créé par un Issuer légitime.
- **Intégrité :** Elle prouve que l'identifiant n'a pas été modifié depuis son émission.

#### 2.4.2 Cryptographie à clé publique/privée

Les signatures numériques sont créées à l'aide de la cryptographie à clé publique/privée.
Voici comment cela fonctionne :

1. **L'Issuer possède une paire de clés :** une **clé privée**, qui est gardée secrète et
   sécurisée, et une **clé publique** correspondante, qui est rendue publiquement
   disponible.
2. **Signature :** Lorsque l'Issuer crée un identifiant, il utilise sa **clé privée** pour
   générer une signature numérique unique pour les données de l'identifiant.
3. **Vérification :** Un Verifier peut ensuite utiliser la **clé publique** de l'Issuer
   pour vérifier la signature. Si la vérification réussit, le Verifier sait que
   l'identifiant est authentique et n'a pas été altéré.

Dans notre implémentation, nous générerons une paire de clés à courbe elliptique (EC) et
utiliserons l'algorithme `ES256` pour signer le [JWT](https://www.corbado.com/fr/glossary/jwks)-VC. La clé
publique est intégrée dans le DID de l'Issuer (`did:web`), permettant à n'importe quel
Verifier de la découvrir et de valider la signature de l'identifiant.\
**Remarque :** Le claim `aud` (audience) est intentionnellement omis dans nos
[JWT](https://www.corbado.com/fr/glossary/jwks), car l'identifiant est conçu pour être polyvalent et non lié à un
wallet spécifique.\
Si vous souhaitez restreindre l'utilisation à une audience particulière, incluez un claim
`aud` et définissez-le en conséquence.

## 3. Aperçu de l'architecture

Notre application Issuer est construite comme un projet Next.js
[full-stack](https://www.corbado.com/fr/blog/application-crud-react-nodejs-express-mysql), avec une séparation
claire entre la logique frontend et backend. Cette architecture nous permet de créer une
expérience utilisateur transparente tout en gérant toutes les opérations critiques pour la
sécurité sur le serveur.\
**Important :** Les tables `verification_sessions` et `verified_credentials` incluses dans
le SQL ne sont pas requises pour cet émetteur mais sont incluses pour être complet.

- **Frontend (`src/app/issue/page.tsx`) :** Une seule page [React](https://www.corbado.com/blog/react-passkeys)
  qui permet aux utilisateurs de saisir leurs données pour demander un identifiant. Elle
  effectue des appels API vers notre backend pour lancer le processus d'émission.
- **Routes API Backend (`src/app/api/issue/...`) :** Un ensemble de points de terminaison
  côté serveur qui implémentent le protocole OpenID4VCI.
    - `/.well-known/openid-credential-issuer` : Un point de terminaison de métadonnées
      public. C'est la première URL qu'un wallet vérifiera pour découvrir les capacités de
      l'émetteur, y compris son serveur d'autorisation, son point de terminaison de jeton,
      son point de terminaison d'identifiant et les types d'identifiants qu'il propose.
    - `/.well-known/openid-configuration` : Un point de terminaison de découverte OpenID
      Connect standard. Bien que étroitement lié au précédent, ce point de terminaison
      sert une configuration plus large liée à OIDC et est souvent requis pour
      l'interopérabilité avec les clients OpenID standard.
    - `/.well-known/did.json` : Le document DID pour notre émetteur. Lors de l'utilisation
      de la méthode `did:web`, ce fichier est utilisé pour publier les clés publiques de
      l'émetteur, que les vérificateurs peuvent utiliser pour valider les signatures des
      identifiants qu'il émet.
    - `authorize/route.ts` : Crée un `pre-authorized_code` et une offre d'identifiant.
    - `token/route.ts` : Échange le `pre-authorized_code` contre un
      [access token](https://www.corbado.com/glossary/access-token).
    - `credential/route.ts` : Émet le JWT-VC final, signé cryptographiquement.
    - `schemas/pid/route.ts` : Expose le schéma JSON pour l'identifiant PID. Cela permet à
      tout consommateur de l'identifiant de comprendre sa structure et ses types de
      données.
- **Bibliothèque (`src/lib/`) :**
    - `database.ts` : Gère toutes les interactions avec la base de données, comme le
      stockage des codes d'autorisation et des clés de l'émetteur.
    - `crypto.ts` : Gère toutes les opérations cryptographiques, y compris la génération
      de clés et la signature JWT.

Voici un diagramme illustrant le flux d'émission :

![Flux d'émission d'un identifiant numérique](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/Mermaid_Chart_Create_complex_visual_diagrams_with_text_A_smarter_way_of_creating_diagrams_2025_07_29_145228_d28fd13731.svg)

## 4. Construire l'Issuer

Maintenant que nous avons une solide compréhension des normes, des protocoles et de
l'architecture, nous pouvons commencer à construire notre émetteur.

> **Suivez pas à pas 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.
>
> ```bash
> 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.

```bash
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 les JWT, les connexions à la
base de données et la génération d'UUID.

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

Cette commande installe :

- `jose` : Pour signer et vérifier les JSON Web Tokens (JWT).
- `mysql2` : Le client [MySQL](https://www.corbado.com/blog/passkey-webauthn-database-guide) pour notre base de
  données.
- `uuid` : Pour générer des chaînes de défi 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](https://www.corbado.com/blog/passkey-webauthn-database-guide)
pour stocker les codes d'autorisation, les sessions d'émission et les clés de l'émetteur.
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 partez de zéro, créez un fichier nommé `docker-compose.yml` avec le contenu suivant :

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

volumes:
    mysql_data:
```

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 pour le vérificateur et l'émetteur
:

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

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

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

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

-- ISSUER TABLES

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

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

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

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

Une fois les deux fichiers en place, ouvrez votre terminal à la racine du projet et
exécutez :

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

Cette commande démarrera un conteneur MySQL en arrière-plan, prêt à être utilisé par notre
application.

### 4.2 Implémentation des bibliothèques partagées

Avant de construire les points de terminaison de l'API, créons les bibliothèques partagées
qui géreront la logique métier de base. Cette approche maintient nos routes API propres et
axées sur la gestion des requêtes HTTP, tandis que le travail complexe est délégué à ces
modules.

#### 4.2.1 La bibliothèque de base de données (`src/lib/database.ts`)

Ce fichier est la seule source de vérité pour toutes les interactions avec la base de
données. Il utilise la bibliothèque `mysql2` pour se connecter à notre conteneur MySQL et
fournit un ensemble de fonctions exportées pour créer, lire et mettre à jour des
enregistrements dans nos tables. Cette couche d'abstraction rend notre code plus modulaire
et plus facile à maintenir.

Créez le fichier `src/lib/database.ts` avec le contenu suivant :

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

// Configuration de la connexion à la base de données
const dbConfig = {
    host: process.env.DATABASE_HOST || "localhost",
    port: parseInt(process.env.DATABASE_PORT || "3306"),
    user: process.env.DATABASE_USER || "app_user",
    password: process.env.DATABASE_PASSWORD || "app_password",
    database: process.env.DATABASE_NAME || "digital_credentials",
    timezone: "+00:00",
};

let connection: mysql.Connection | null = null;

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

// Fonctions Data-Access-Object (DAO) pour chaque table
// ... (par ex., createChallenge, getChallenge, createAuthorizationCode, etc.)
```

> **Remarque :** Pour des raisons de brièveté, la liste complète des fonctions DAO a été
> omise. Vous pouvez trouver le code complet dans le
> [dépôt du projet](https://github.com/corbado/digital-credentials-example/blob/main/src/lib/database.ts).
> Ce fichier inclut des fonctions pour gérer les défis, les sessions de vérification, les
> codes d'autorisation, les sessions d'émission et les clés de l'émetteur.

#### 4.2.2 La bibliothèque de cryptographie (`src/lib/crypto.ts`)

Ce fichier gère toutes les opérations cryptographiques critiques pour la sécurité. Il
utilise la bibliothèque `jose` pour générer des paires de clés et signer des JSON Web
Tokens (JWT).

**Génération de clés** La fonction `generateIssuerKeyPair` crée une nouvelle paire de clés
à courbe elliptique qui sera utilisée pour signer les identifiants. La clé publique est
exportée au format JSON Web Key (JWK) afin qu'elle puisse être publiée dans notre document
`did.json`.

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

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

    const publicKeyJWK = await exportJWK(publicKey);
    publicKeyJWK.kid = keyId; // Attribuer un ID de clé unique

    // ... (exportation de la clé privée et autre configuration)

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

**Création d'identifiants JWT** La fonction `createJWTVerifiableCredential` est au cœur du
processus d'émission. Elle prend les attestations de l'utilisateur, la paire de clés de
l'émetteur et d'autres métadonnées, et les utilise pour créer un JWT-VC signé.

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

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

    const vcPayload = {
        // Le DID de l'émetteur
        iss: issuerKeyPair.issuerDid,
        // Le DID du sujet (détenteur)
        sub: subjectId,
        // L'heure à laquelle l'identifiant a été émis (iat) et quand il expire (exp)
        iat: now,
        exp: now + oneYear,
        // Le modèle de données du Verifiable Credential
        vc: {
            "@context": [
                "https://www.w3.org/2018/credentials/v1",
                "https://europa.eu/eudi/pid/v1",
            ],
            type: ["VerifiableCredential", "eu.europa.ec.eudi.pid.1"],
            issuer: issuerKeyPair.issuerDid,
            issuanceDate: new Date(now * 1000).toISOString(),
            credentialSubject: {
                id: subjectId,
                ...claims,
            },
        },
    };

    // Signer la charge utile avec la clé privée de l'émetteur
    return await new SignJWT(vcPayload)
        .setProtectedHeader({
            alg: issuerKeyPair.algorithm,
            kid: issuerKeyPair.keyId,
            typ: "JWT",
        })
        .sign(issuerKeyPair.privateKey);
}
```

Cette fonction construit la charge utile du JWT conformément au modèle de données W3C
Verifiable Credentials et la signe avec la clé privée de l'émetteur, produisant un
[Verifiable Credential](https://www.corbado.com/glossary/verifiable-credential) sécurisé et vérifiable.

### 4.2 Aperçu 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, même s'ils font partie du même projet. Ceci est réalisé en tirant
parti de l'App Router pour les pages d'interface utilisateur et les points de terminaison
de l'API.

- **Frontend (`src/app/issue/page.tsx`) :** Un seul composant de page
  [React](https://www.corbado.com/blog/react-passkeys) qui définit l'interface utilisateur pour la route
  `/issue`. Il gère la saisie de l'utilisateur et communique avec notre API backend.

- **Routes API Backend (`src/app/api/...`) :**
    - **Découverte (`.well-known/.../route.ts`) :** Ces routes exposent des points de
      terminaison de métadonnées publics qui permettent aux wallets et autres clients de
      découvrir les capacités de l'émetteur et ses clés publiques.
    - **Émission (`issue/.../route.ts`) :** Ces points de terminaison implémentent la
      logique de base d'OpenID4VCI, y compris la création d'offres d'identifiants,
      l'émission de jetons et la signature de l'identifiant final.
    - **Schéma (`schemas/pid/route.ts`) :** Cette route sert le schéma JSON pour
      l'identifiant, définissant sa structure.

- **Bibliothèque (`src/lib/`) :** Ce répertoire contient une logique réutilisable partagée
  à travers le backend.
    - `database.ts` : Gère toutes les interactions avec la base de données, en abstrayant
      les requêtes SQL.
    - `crypto.ts` : Gère toutes les opérations cryptographiques, telles que la génération
      de clés et la signature JWT.

Cette séparation claire rend l'application modulaire et plus facile à maintenir.

**Remarque :** La fonction `generateIssuerDid()` doit retourner un `did:web` valide
correspondant à votre domaine d'émetteur.\
Lors du déploiement, le `.well-known/did.json` doit être servi via HTTPS sur ce domaine
pour que les vérificateurs puissent valider les identifiants.

![Aperçu de l'architecture de l'application Next.js](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/Mermaid_Chart_Create_complex_visual_diagrams_with_text_A_smarter_way_of_creating_diagrams_2025_07_29_151549_6a0aca6477.svg)

### 4.3 Construire le frontend

Notre frontend est une seule page React qui fournit un formulaire simple pour que les
utilisateurs demandent un nouvel identifiant numérique. Ses responsabilités sont de :

- Capturer les données de l'utilisateur (nom, date de naissance, etc.).
- Envoyer ces données à notre backend pour créer une offre d'identifiant.
- Afficher le QR code et le code PIN résultants pour que l'utilisateur les scanne avec son
  wallet.

La logique de base est gérée dans la fonction `handleSubmit`, qui est déclenchée lorsque
l'utilisateur soumet le formulaire.

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

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

    try {
        // 1. Valider les champs requis
        if (!userData.given_name || !userData.family_name || !userData.birth_date) {
            throw new Error("Veuillez remplir tous les champs requis");
        }

        // 2. Demander une offre d'identifiant au backend
        const response = await fetch("/api/issue/authorize", {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
            },
            body: JSON.stringify({
                user_data: userData,
            }),
        });

        if (!response.ok) {
            const errorData = await response.json();
            throw new Error(
                errorData.error_description ||
                    "Échec de la création de l'offre d'identifiant",
            );
        }

        // 3. Définir l'offre d'identifiant dans l'état pour afficher le QR code
        const result = await response.json();
        setCredentialOffer(result);
    } catch (err) {
        const errorMessage =
            (err as Error).message || "Une erreur inconnue s'est produite";
        setError(errorMessage);
    } finally {
        setLoading(false);
    }
};
```

Cette fonction effectue trois actions clés :

1. **Valide les données du formulaire** pour s'assurer que tous les champs requis sont
   remplis.
2. **Envoie une requête `POST`** à notre point de terminaison `/api/issue/authorize` avec
   les données de l'utilisateur.
3. **Met à jour l'état du composant** avec l'offre d'identifiant reçue du backend, ce qui
   déclenche l'affichage du QR code et du code de transaction dans l'interface
   utilisateur.

Le reste du fichier contient du code React standard pour le rendu du formulaire et
l'affichage du QR code. Vous pouvez voir le fichier complet dans le
[dépôt du projet](https://github.com/corbado/digital-credentials-example/blob/main/src/app/issue/page.tsx).

### 4.4 Configurer l'environnement et la découverte

Avant de construire l'API backend, nous devons configurer notre environnement et mettre en
place les points de terminaison de découverte. Ces fichiers `.well-known` sont cruciaux
pour que les wallets trouvent notre émetteur et comprennent comment interagir avec lui.

#### 4.4.1 Créer le fichier d'environnement

Créez un fichier nommé `.env.local` à la racine de votre projet et ajoutez la ligne
suivante. Cette URL doit être publiquement accessible pour qu'un wallet mobile puisse
l'atteindre. Pour le développement local, vous pouvez utiliser un service de tunnel comme
[ngrok](https://www.corbado.com/blog/multi-device-passkey-login-corbado-ngrok) pour exposer votre `localhost`.

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

#### 4.4.2 Implémenter les points de terminaison de découverte

Les wallets découvrent les capacités d'un émetteur en interrogeant des URL `.well-known`
standard. Nous devons créer trois de ces points de terminaison.

**1. Métadonnées de l'émetteur (`/.well-known/openid-credential-issuer`)**

C'est le fichier de découverte principal pour OpenID4VCI. Il indique au wallet tout ce
qu'il doit savoir sur l'émetteur, y compris ses points de terminaison, les types
d'identifiants qu'il propose et les algorithmes cryptographiques pris en charge.

Créez le fichier `src/app/.well-known/openid-credential-issuer/route.ts` :

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

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

    const issuerMetadata = {
        // L'identifiant unique de l'émetteur.
        issuer: baseUrl,
        // L'URL du serveur d'autorisation. Pour simplifier, notre émetteur est son propre serveur d'autorisation.
        authorization_servers: [baseUrl],
        // L'URL de l'émetteur d'identifiants.
        credential_issuer: baseUrl,
        // Le point de terminaison où le wallet enverra une requête POST pour recevoir l'identifiant réel.
        credential_endpoint: `${baseUrl}/api/issue/credential`,
        // Le point de terminaison où le wallet échange un code d'autorisation contre un jeton d'accès.
        token_endpoint: `${baseUrl}/api/issue/token`,
        // Le point de terminaison pour le flux d'autorisation (non utilisé dans notre flux pré-autorisé, mais bonne pratique de l'inclure).
        authorization_endpoint: `${baseUrl}/api/issue/authorize`,
        // Indique la prise en charge du flux de code pré-autorisé sans nécessiter d'authentification du client.
        pre_authorized_grant_anonymous_access_supported: true,
        // Informations lisibles par l'homme sur l'émetteur.
        display: [
            {
                name: "Corbado Credentials Issuer",
                locale: "en-US",
            },
        ],
        // Une liste des types d'identifiants que cet émetteur peut émettre.
        credential_configurations_supported: {
            "eu.europa.ec.eudi.pid.1": {
                // Le format de l'identifiant (par ex., jwt_vc, mso_mdoc).
                format: "jwt_vc",
                // Le type de document spécifique, conforme aux normes ISO mDoc.
                doctype: "eu.europa.ec.eudi.pid.1",
                // La portée OAuth 2.0 associée à ce type d'identifiant.
                scope: "eu.europa.ec.eudi.pid.1",
                // Méthodes que le wallet peut utiliser pour prouver la possession de sa clé.
                cryptographic_binding_methods_supported: ["jwk"],
                // Algorithmes de signature que l'émetteur prend en charge pour cet identifiant.
                credential_signing_alg_values_supported: ["ES256"],
                // Types de preuve de possession que le wallet peut utiliser.
                proof_types_supported: {
                    jwt: {
                        proof_signing_alg_values_supported: ["ES256", "ES384", "ES512"],
                    },
                },
                // Propriétés d'affichage pour l'identifiant.
                display: [
                    {
                        name: "Corbado Credential Issuer",
                        locale: "en-US",
                        logo: {
                            uri: `${baseUrl}/logo.png`,
                            alt_text: "EU Digital Identity",
                        },
                        background_color: "#003399",
                        text_color: "#FFFFFF",
                    },
                ],
                // Une liste des attestations (attributs) dans l'identifiant.
                claims: {
                    "eu.europa.ec.eudi.pid.1": {
                        given_name: {
                            mandatory: true,
                            display: [{ name: "Given Name", locale: "en-US" }],
                        },
                        family_name: {
                            mandatory: true,
                            display: [{ name: "Family Name", locale: "en-US" }],
                        },
                        birth_date: {
                            mandatory: true,
                            display: [{ name: "Date of Birth", locale: "en-US" }],
                        },
                    },
                },
            },
        },
        // Méthodes d'authentification prises en charge par le point de terminaison de jeton. 'none' signifie client public.
        token_endpoint_auth_methods_supported: ["none"],
        // Méthodes de défi de code PKCE prises en charge.
        code_challenge_methods_supported: ["S256"],
        // Types de subvention OAuth 2.0 que l'émetteur prend en charge.
        grant_types_supported: [
            "authorization_code",
            "urn:ietf:params:oauth:grant-type:pre-authorized_code",
        ],
    };

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

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

Il s'agit d'un document de découverte OIDC standard qui fournit un ensemble plus large de
détails de configuration.

Créez le fichier `src/app/.well-known/openid-configuration/route.ts` :

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

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

    const openidConfiguration = {
        // L'identifiant unique de l'émetteur.
        credential_issuer: baseUrl,
        // Le point de terminaison où le wallet enverra une requête POST pour recevoir l'identifiant réel.
        credential_endpoint: `${baseUrl}/api/issue/credential`,
        // Le point de terminaison pour le flux d'autorisation.
        authorization_endpoint: `${baseUrl}/api/issue/authorize`,
        // Le point de terminaison où le wallet échange un code d'autorisation contre un jeton d'accès.
        token_endpoint: `${baseUrl}/api/issue/token`,
        // Une liste des types d'identifiants que cet émetteur peut émettre.
        credential_configurations_supported: {
            "eu.europa.ec.eudi.pid.1": {
                format: "jwt_vc",
                scope: "eu.europa.ec.eudi.pid.1",
                cryptographic_binding_methods_supported: ["jwk"],
                credential_signing_alg_values_supported: ["ES256", "ES384", "ES512"],
                proof_types_supported: {
                    jwt: {
                        proof_signing_alg_values_supported: ["ES256", "ES384", "ES512"],
                    },
                },
            },
        },
        // Types de subvention OAuth 2.0 que l'émetteur prend en charge.
        grant_types_supported: [
            "authorization_code",
            "urn:ietf:params:oauth:grant-type:pre-authorized_code",
        ],
        // Indique la prise en charge du flux de code pré-autorisé.
        pre_authorized_grant_anonymous_access_supported: true,
        // Méthodes de défi de code PKCE prises en charge.
        code_challenge_methods_supported: ["S256"],
        // Méthodes d'authentification prises en charge par le point de terminaison de jeton.
        token_endpoint_auth_methods_supported: ["none"],
        // Portées OAuth 2.0 que l'émetteur prend en charge.
        scopes_supported: ["eu.europa.ec.eudi.pid.1"],
    };

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

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

Ce fichier publie la clé publique de l'émetteur en utilisant la méthode `did:web`,
permettant à quiconque de vérifier la signature des identifiants qu'il a émis.

Créez le fichier `src/app/.well-known/did.json/route.ts` :

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

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

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

    const publicKeyJWK = JSON.parse(issuerKey.public_key);
    const didId = generateIssuerDid();
    const didDocument = {
        // Le contexte définit le vocabulaire utilisé dans le document.
        "@context": [
            "https://www.w3.org/ns/did/v1",
            "https://w3id.org/security/suites/jws-2020/v1",
        ],
        // L'URI du DID, qui est l'identifiant unique de l'émetteur.
        id: didId,
        // Le contrôleur du DID, qui est l'entité qui contrôle le DID. Ici, c'est l'émetteur lui-même.
        controller: didId,
        // Une liste de clés publiques qui peuvent être utilisées pour vérifier les signatures de l'émetteur.
        verificationMethod: [
            {
                // Un identifiant unique pour la clé, dans le périmètre du DID.
                id: `${didId}#${issuerKey.key_id}`,
                // Le type de la clé.
                type: "JsonWebKey2020",
                // Le DID du contrôleur de la clé.
                controller: didId,
                // La clé publique au format JWK.
                publicKeyJwk: publicKeyJWK,
            },
        ],
        // Spécifie quelles clés peuvent être utilisées pour l'authentification (prouver le contrôle du DID).
        authentication: [`${didId}#${issuerKey.key_id}`],
        // Spécifie quelles clés peuvent être utilisées pour créer des Verifiable Credentials.
        assertionMethod: [`${didId}#${issuerKey.key_id}`],
        // Une liste de services fournis par le sujet du DID, comme le point de terminaison de l'émetteur.
        service: [
            {
                id: `${didId}#openid-credential-issuer`,
                type: "OpenIDCredentialIssuer",
                serviceEndpoint: `${baseUrl}/.well-known/openid-credential-issuer`,
            },
        ],
    };

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

> **Pourquoi ne pas utiliser de cache ?** Vous remarquerez que ces trois points de
> terminaison retournent des en-têtes qui empêchent agressivement la mise en cache
> (`Cache-Control: no-cache`, `Pragma: no-cache`, `Expires: 0`). C'est une pratique de
> sécurité essentielle pour les documents de découverte. Les configurations de l'émetteur
> peuvent changer, par exemple, une clé cryptographique peut être renouvelée. Si un wallet
> ou un client mettait en cache une ancienne version du fichier `did.json` ou
> `openid-credential-issuer`, il ne parviendrait pas à valider les nouveaux identifiants
> ou à interagir avec les points de terminaison mis à jour. En forçant les clients à
> récupérer une nouvelle copie à chaque requête, nous nous assurons qu'ils disposent
> toujours des informations les plus récentes.

#### 4.4.3 Implémenter le point de terminaison du schéma de l'identifiant

La dernière pièce de notre infrastructure publique est le point de terminaison du schéma
de l'identifiant. Cette route sert un schéma JSON qui définit formellement la structure,
les types de données et les contraintes de l'identifiant PID que nous émettons. Les
wallets et les vérificateurs peuvent utiliser ce schéma pour valider le contenu de
l'identifiant.

Créez le fichier `src/app/api/schemas/pid/route.ts` avec le contenu suivant :

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

export async function GET() {
    const schema = {
        $schema: "https://json-schema.org/draft/2020-12/schema",
        $id: "https://example.com/schemas/pid", // Remplacez par votre domaine réel
        title: "PID Credential",
        description:
            "A schema for a Verifiable Credential representing a Personal Identification Document (PID).",
        type: "object",
        properties: {
            credentialSubject: {
                type: "object",
                properties: {
                    given_name: { type: "string" },
                    family_name: { type: "string" },
                    birth_date: { type: "string", format: "date" },
                    // ... autres propriétés du sujet de l'identifiant
                },
                required: ["given_name", "family_name", "birth_date"],
            },
            // ... autres propriétés de haut niveau d'un Verifiable Credential
        },
    };

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

> **Remarque :** Le schéma JSON pour un identifiant PID peut être assez volumineux et
> détaillé. Pour des raisons de brièveté, le schéma complet a été tronqué. Vous pouvez
> trouver le fichier complet dans le
> [dépôt du projet](https://github.com/corbado/digital-credentials-example/blob/main/src/app/api/schemas/pid/route.ts).

### 4.5 Construire les points de terminaison du backend

Avec le frontend en place, nous avons maintenant besoin de la logique côté serveur pour
gérer le flux OpenID4VCI. Nous commencerons par le premier point de terminaison que le
frontend appelle : `/api/issue/authorize`.

#### 4.5.1 `/api/issue/authorize` : Créer l'offre d'identifiant

Ce point de terminaison est chargé de prendre les données de l'utilisateur, de générer un
code sécurisé à usage unique et de construire une `credential_offer` que le wallet de
l'utilisateur peut comprendre.

Voici la logique de base :

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

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

        // 1. Valider les données de l'utilisateur
        if (
            !user_data ||
            !user_data.given_name ||
            !user_data.family_name ||
            !user_data.birth_date
        ) {
            return NextResponse.json({ error: "missing_user_data" }, { status: 400 });
        }

        // 2. Générer un code pré-autorisé et un code PIN
        const code = uuidv4();
        const expiresAt = new Date(Date.now() + 10 * 60 * 1000); // 10 minutes
        const txCode = Math.floor(1000 + Math.random() * 9000).toString(); // PIN à 4 chiffres

        // 3. Stocker le code et les données de l'utilisateur
        await createAuthorizationCode(uuidv4(), code, expiresAt);
        // Remarque : Ceci utilise un stockage en mémoire à des fins de démonstration uniquement.
        // En production, persistez les données de manière sécurisée dans une base de données avec une expiration appropriée.
        if (!(global as any).userDataStore) (global as any).userDataStore = new Map();
        (global as any).userDataStore.set(code, user_data);
        if (!(global as any).txCodeStore) (global as any).txCodeStore = new Map();
        (global as any).txCodeStore.set(code, txCode);

        // 4. Créer l'objet de l'offre d'identifiant
        const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000";
        const credentialOffer = {
            // L'identifiant de l'émetteur, qui est son URL de base.
            credential_issuer: baseUrl,
            // Un tableau des types d'identifiants que l'émetteur propose.
            credential_configuration_ids: ["eu.europa.ec.eudi.pid.1"],
            // Spécifie les types de subvention que le wallet peut utiliser.
            grants: {
                // Nous utilisons le flux de code pré-autorisé.
                "urn:ietf:params:oauth:grant-type:pre-authorized_code": {
                    // Le code à usage unique que le wallet échangera contre un jeton.
                    "pre-authorized_code": code,
                    // Indique que l'utilisateur doit entrer un code PIN (tx_code) pour utiliser le code.
                    user_pin_required: true,
                },
            },
        };

        // 5. Créer l'URI complète de l'offre d'identifiant (un lien profond pour les wallets)
        const credentialOfferUri = `openid-credential-offer://?credential_offer=${encodeURIComponent(
            JSON.stringify(credentialOffer),
        )}`;

        // La réponse finale au frontend.
        return NextResponse.json({
            // Le lien profond pour le QR code.
            credential_offer_uri: credentialOfferUri,
            // Le code pré-autorisé brut, pour affichage ou saisie manuelle.
            pre_authorized_code: code,
            // Le code PIN à 4 chiffres que l'utilisateur doit entrer dans son wallet.
            tx_code: txCode,
        });
    } catch (error) {
        console.error("Authorization error:", error);
        return NextResponse.json({ error: "server_error" }, { status: 500 });
    }
}
```

Étapes clés de ce point de terminaison :

1. **Validation des données :** Il s'assure d'abord que les données utilisateur requises
   sont présentes.
2. **Génération de codes :** Il crée un `pre-authorized_code` unique (un UUID) et un
   `tx_code` à 4 chiffres (PIN) pour une couche de sécurité supplémentaire.
3. **Persistance des données :** Le `pre-authorized_code` est stocké dans la base de
   données avec un court délai d'expiration. Les données de l'utilisateur et le PIN sont
   stockés en mémoire, liés au code.
4. **Construction de l'offre :** Il construit l'objet `credential_offer` conformément à la
   spécification OpenID4VCI. Cet objet indique au wallet où se trouve l'émetteur, quels
   identifiants il propose et le code nécessaire pour les obtenir.
5. **Retour de l'URI :** Enfin, il crée une URI de lien profond
   (`openid-credential-offer://...`) et la retourne au frontend, avec le `tx_code` que
   l'utilisateur doit voir.

#### 4.5.2 `/api/issue/token` : Échanger le code contre un jeton

Une fois que l'utilisateur a scanné le QR code et entré son PIN, le wallet effectue une
requête `POST` à ce point de terminaison. Son travail consiste à valider le
`pre-authorized_code` et le `user_pin` (PIN), et s'ils sont valides, à émettre un access
token de courte durée.

Créez le fichier `src/app/api/issue/token/route.ts` avec le contenu suivant :

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

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

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

        // 2. Valider le code pré-autorisé
        const authCode = await getAuthorizationCode(code);
        if (!authCode) {
            return NextResponse.json(
                {
                    error: "invalid_grant",
                    error_description: "Invalid or expired code",
                },
                { status: 400 },
            );
        }

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

        // 4. Générer le jeton d'accès et le c_nonce
        const accessToken = uuidv4();
        const cNonce = uuidv4();
        const cNonceExpiresAt = new Date(Date.now() + 5 * 60 * 1000); // 5 minutes

        // 5. Créer une nouvelle session d'émission
        const userData = (global as any).userDataStore?.get(code);
        await createIssuanceSession(
            uuidv4(),
            authCode.id,
            accessToken,
            cNonce,
            cNonceExpiresAt,
            userData,
        );

        // 6. Marquer le code comme utilisé et nettoyer les données temporaires
        await markAuthorizationCodeAsUsed(code);
        (global as any).txCodeStore?.delete(code);
        (global as any).userDataStore?.delete(code);

        // 7. Retourner la réponse du jeton d'accès
        return NextResponse.json({
            access_token: accessToken,
            token_type: "Bearer",
            expires_in: 3600, // 1 heure
            c_nonce: cNonce,
            c_nonce_expires_in: 300, // 5 minutes
        });
    } catch (error) {
        console.error("Token endpoint error:", error);
        return NextResponse.json({ error: "server_error" }, { status: 500 });
    }
}
```

Étapes clés de ce point de terminaison :

1. **Validation du type de subvention :** Il s'assure que le wallet utilise le bon type de
   subvention `pre-authorized_code`.
2. **Validation du code :** Il vérifie que le `pre-authorized_code` existe dans la base de
   données, n'est pas expiré et n'a pas été utilisé auparavant.
3. **Validation du PIN :** Il compare le `user_pin` du wallet avec le `tx_code` que nous
   avons stocké précédemment pour s'assurer que l'utilisateur a autorisé la transaction.
4. **Génération de jetons :** Il crée un `access_token` sécurisé et un `c_nonce`
   (credential nonce), qui est une valeur à usage unique pour prévenir les attaques par
   rejeu sur le point de terminaison de l'identifiant.
5. **Création de session :** Il crée un nouvel enregistrement `issuance_sessions` dans la
   base de données, liant l'[access token](https://www.corbado.com/glossary/access-token) aux données de
   l'utilisateur.
6. **Marquer le code comme utilisé :** Pour empêcher que la même offre ne soit utilisée
   deux fois, il marque le `pre-authorized_code` comme utilisé.
7. **Retour du jeton :** Il retourne l'`access_token` et le `c_nonce` au wallet.

#### 4.5.3 `/api/issue/credential` : Émettre l'identifiant signé

C'est le point de terminaison final et le plus important. Le wallet utilise le jeton
d'accès qu'il a reçu du point de terminaison `/token` pour effectuer une requête `POST`
authentifiée à cette route. Le travail de ce point de terminaison est d'effectuer la
validation finale, de créer l'identifiant signé cryptographiquement et de le retourner au
wallet.

Créez le fichier `src/app/api/issue/credential/route.ts` avec le contenu suivant :

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

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

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

        // 2. Obtenir les données de l'utilisateur de la session
        const userData = session.user_data;
        if (!userData) {
            return NextResponse.json({ error: "missing_user_data" }, { status: 400 });
        }

        // 3. Obtenir la clé d'émetteur active
        const issuerKey = await getActiveIssuerKey();
        if (!issuerKey) {
            // Dans une application réelle, vous auriez un système de gestion de clés plus robuste.
            // Pour cette démo, nous pouvons générer une clé à la volée si elle n'existe pas.
            // Cette partie est omise pour des raisons de brièveté mais se trouve dans le dépôt.
            return NextResponse.json(
                {
                    error: "server_error",
                    error_description: "Failed to get issuer key",
                },
                { status: 500 },
            );
        }

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

        // 5. Stocker l'identifiant émis dans la base de données
        await createIssuedCredential(/* ... détails de l'identifiant ... */);
        await updateIssuanceSession(session.id, "credential_issued");

        // 6. Retourner l'identifiant signé
        return NextResponse.json({
            format: "jwt_vc",
            credential: credentialData,
            c_nonce: uuidv4(), // Un nouveau nonce pour les requêtes ultérieures
            c_nonce_expires_in: 300,
        });
    } catch (error) {
        console.error("Credential endpoint error:", error);
        return NextResponse.json({ error: "server_error" }, { status: 500 });
    }
}
```

Étapes clés de ce point de terminaison :

1. **Validation du jeton :** Il vérifie la présence d'un jeton `Bearer` valide dans
   l'en-tête `Authorization` et l'utilise pour rechercher la session d'émission active.
2. **Récupération des données utilisateur :** Il récupère les données d'attestations de
   l'utilisateur, qui ont été stockées dans la session lors de la création du jeton.
3. **Chargement de la clé de l'émetteur :** Il charge la clé de signature active de
   l'émetteur depuis la base de données. Dans un scénario réel, cela serait géré par un
   système de gestion de clés sécurisé.
4. **Création de l'identifiant :** Il appelle notre aide `createJWTVerifiableCredential`
   de `src/lib/crypto.ts` pour construire et signer le JWT-VC.
5. **Journalisation de l'émission :** Il enregistre un enregistrement de l'identifiant
   émis dans la base de données à des fins d'audit et de révocation.
6. **Retour de l'identifiant :** Il retourne l'identifiant signé au wallet dans une
   réponse JSON. Le wallet est alors responsable de le stocker en toute sécurité.

## 5. Exécution de l'Issuer et prochaines étapes

Vous disposez maintenant d'une implémentation complète et de bout en bout d'un émetteur
d'identifiants numériques. Voici comment l'exécuter localement et ce que vous devez
considérer pour le faire passer d'une preuve de concept à une application prête pour la
production.

### 5.1 Comment exécuter l'exemple

1. **Clonez le dépôt :**

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

2. **Installez les dépendances :**

    ```bash
    npm install
    ```

3. **Démarrez la base de données :** Assurez-vous que Docker est en cours d'exécution,
   puis démarrez le conteneur MySQL :

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

4. **Configurez l'environnement et lancez le tunnel :** C'est l'étape la plus critique
   pour les tests locaux. Étant donné que votre wallet mobile doit se connecter à votre
   machine de développement via Internet, vous devez exposer votre serveur local avec une
   URL HTTPS publique. Nous utiliserons `ngrok` pour cela.

    a. **Démarrez ngrok :**

    ```bash
    ngrok http 3000
    ```

    b. **Copiez l'URL HTTPS** depuis la sortie de
    [ngrok](https://www.corbado.com/blog/multi-device-passkey-login-corbado-ngrok) (par ex.,
    `https://random-string.ngrok.io`). c. **Créez un fichier `.env.local`** et définissez
    l'URL :

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

5. **Exécutez l'application :**

    ```bash
    npm run dev
    ```

    Ouvrez votre navigateur à l'adresse `http://localhost:3000/issue`. Vous pouvez
    maintenant remplir le formulaire, et le QR code généré pointera correctement vers
    votre URL [ngrok](https://www.corbado.com/blog/multi-device-passkey-login-corbado-ngrok) publique, permettant
    à votre wallet mobile de se connecter et de recevoir l'identifiant.

### 5.2 L'importance de HTTPS et `ngrok`

Les protocoles d'identifiants numériques sont conçus avec la sécurité comme priorité
absolue. Pour cette raison, les wallets refuseront presque toujours de se connecter à un
émetteur via une connexion non sécurisée (`http://`). L'ensemble du processus repose sur
une connexion **HTTPS** sécurisée, qui est activée par un **certificat SSL**.

Un service de tunnel comme `ngrok` résout ces deux problèmes en créant une URL HTTPS
publique et sécurisée (avec un certificat SSL valide) qui transfère tout le trafic vers
votre serveur de développement local.\
Les wallets nécessitent HTTPS et refuseront de se connecter à des points de terminaison
non sécurisés (`http://`). C'est un outil essentiel pour tester tout service web qui doit
interagir avec des appareils mobiles ou des webhooks externes.

### 5.3 Ce qui est hors du champ de ce tutoriel

Cet exemple est intentionnellement axé sur le flux d'émission de base 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 :** L'émetteur est à des fins éducatives. Un système
  de production nécessiterait un système de gestion de clés (KMS) sécurisé au lieu de
  stocker les clés dans une base de données, une gestion robuste des erreurs, une
  limitation du débit et une journalisation d'audit complète.
- **Révocation des identifiants :** Ce guide n'implémente pas de mécanisme de révocation
  des identifiants émis.\
  Bien que le schéma inclue un indicateur `revoked` pour une utilisation future, aucune
  logique de révocation n'est fournie ici.
- **Flux de code d'autorisation :** Nous nous sommes concentrés exclusivement sur le flux
  `pre-authorized_code`. Une implémentation complète du flux `authorization_code`
  nécessiterait un écran de consentement de l'utilisateur et une logique
  [OAuth 2.0](https://www.corbado.com/glossary/oauth2) plus complexe.
- **Gestion des utilisateurs :** Le guide n'inclut aucune
  [authentification](https://www.corbado.com/fr/blog/comment-passer-totalement-sans-mot-de-passe) ou gestion des
  utilisateurs pour l'émetteur lui-même. Il est supposé que l'utilisateur est déjà
  authentifié et autorisé à recevoir un identifiant.

## 6. Conclusion

C'est tout ! Avec quelques pages de code, nous avons maintenant un émetteur d'identifiants
numériques complet et de bout en bout qui :

1. Fournit un frontend convivial pour demander des identifiants.
2. Implémente le flux complet `pre-authorized_code` d'OpenID4VCI.
3. Expose tous les points de terminaison de découverte nécessaires à l'interopérabilité
   des wallets.
4. Génère et signe un JWT-[Verifiable Credential](https://www.corbado.com/glossary/verifiable-credential)
   sécurisé et conforme aux normes.

Bien que ce guide fournisse une base solide, un émetteur prêt pour la production
nécessiterait des fonctionnalités supplémentaires comme une gestion robuste des clés, un
stockage persistant au lieu de stockages en mémoire, la révocation des identifiants et un
renforcement complet de la sécurité.\
La compatibilité des wallets varie également ; Sphereon Wallet est recommandé pour les
tests, mais d'autres wallets pourraient ne pas prendre en charge le flux pré-autorisé tel
qu'implémenté ici. Cependant, les éléments de base et le flux d'interaction resteraient
les mêmes. En suivant ces modèles, vous pouvez construire un émetteur sécurisé et
interopérable pour tout type d'identifiant numérique.

## 7. Ressources

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 :**
    - [Code source complet sur GitHub](https://github.com/corbado/digital-credentials-example)

- **Spécifications clés :**
    - [OpenID for Verifiable Credential Issuance (OpenID4VCI)](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html)
      : Le protocole d'émission principal.
    - [W3C Verifiable Credentials Data Model](https://www.w3.org/TR/vc-data-model/) : La
      norme fondamentale pour les VC.
    - [La méthode `did:web`](https://w3c-ccg.github.io/did-method-web/) : La méthode DID
      utilisée pour la clé publique de notre émetteur.

- **Outils :**
    - [Sphereon Wallet](https://sphereon.com/wallet/) : Le wallet de test utilisé dans ce
      guide.
    - ngrok : Pour créer un tunnel sécurisé vers votre environnement de développement
      local.

- **Bibliothèques :**
    - Next.js : Le framework React pour construire le frontend et le backend.
    - [jose](https://github.com/panva/jose) : Pour créer et signer des JSON Web Tokens
      (JWT).
    - [mysql2](https://github.com/sidorares/node-mysql2) : Le client MySQL pour
      [Node.js](https://www.corbado.com/blog/nodejs-passkeys).
