---
url: 'https://www.corbado.com/de/blog/anleitung-verifiable-credential-issuer-erstellen'
title: 'Wie man einen Issuer für digitale Nachweise erstellt (Entwickler-Guide)'
description: 'Lernen Sie, wie man einen Issuer für W3C Verifiable Credentials mit dem OpenID4VCI-Protokoll erstellt. Diese Schritt-für-Schritt-Anleitung zeigt, wie eine Next.js-Anwendung entwickelt wird, die kryptografisch signierte Nachweise ausstellt, die mit digital'
lang: 'de'
author: 'Amine'
date: '2025-08-20T15:39:18.534Z'
lastModified: '2026-03-27T07:03:14.887Z'
keywords: 'issuer für digitale nachweise, anleitung issuer, issuer erstellen'
category: 'Digital Credentials'
---

# Wie man einen Issuer für digitale Nachweise erstellt (Entwickler-Guide)

## 1. Einführung

[Digitale Nachweise](https://www.corbado.com/de/blog/digital-credentials-api) sind eine leistungsstarke Methode,
um [Identität](https://www.corbado.com/de/blog/digital-credentials-api) und Ansprüche auf eine sichere und
datenschutzfreundliche Weise zu belegen. Aber wie erhalten Nutzer diese Nachweise
überhaupt? Hier wird die Rolle des **Issuers** entscheidend. Ein
[Issuer](https://www.corbado.com/glossary/issuer) ist eine vertrauenswürdige Instanz – wie zum Beispiel eine
[Regierungsbehörde](https://www.corbado.com/passkeys-for-public-sector), eine Universität oder eine Bank –, die
für die Erstellung und Verteilung von digital signierten Nachweisen an Nutzer
verantwortlich ist.

Dieser Guide bietet eine umfassende Schritt-für-Schritt-Anleitung zur Erstellung eines
Issuers für [digitale Nachweise](https://www.corbado.com/de/blog/digital-credentials-api). Wir konzentrieren uns
auf das Protokoll **OpenID for Verifiable Credential Issuance (OpenID4VCI)**, einen
modernen Standard, der festlegt, wie Nutzer Nachweise von einem [Issuer](https://www.corbado.com/glossary/issuer)
erhalten und sicher in ihren digitalen Wallets speichern können.

Das Endergebnis wird eine funktionierende [Next.js](https://www.corbado.com/blog/nextjs-passkeys)-Anwendung sein,
die Folgendes kann:

1. Nutzerdaten über ein einfaches Webformular entgegennehmen.
2. Ein sicheres, einmaliges Credential-Angebot erstellen.
3. Das Angebot als QR-Code anzeigen, den der Nutzer mit seiner mobilen
   [Wallet](https://www.corbado.com/blog/digital-wallet-assurance) scannen kann.
4. Einen kryptografisch signierten Nachweis ausstellen, den der Nutzer speichern und zur
   Verifizierung vorlegen kann.

### 1.1 Terminologie: Digitale Nachweise vs. Verifiable Credentials

Bevor wir fortfahren, ist es wichtig, den Unterschied zwischen zwei verwandten, aber
unterschiedlichen Konzepten zu klären:

- **Digitale Nachweise (allgemeiner Begriff):** Dies ist eine breite Kategorie, die jede
  digitale Form von Nachweisen, Zertifikaten oder Bestätigungen (Attestations) umfasst.
  Dazu können einfache digitale Zertifikate, grundlegende digitale Badges oder jeder
  elektronisch gespeicherte Nachweis gehören, der kryptografische Sicherheitsmerkmale
  haben kann oder auch nicht.

- **Verifiable Credentials (VCs – W3C-Standard):** Dies ist ein spezifischer Typ digitaler
  Nachweise, der dem W3C-Standard für das
  „[Verifiable Credentials](https://www.corbado.com/glossary/microcredentials) Data Model“ folgt.
  [Verifiable Credentials](https://www.corbado.com/glossary/microcredentials) sind kryptografisch signierte,
  manipulationssichere und datenschutzfreundliche Nachweise, die unabhängig überprüft
  werden können. Sie beinhalten spezifische technische Anforderungen wie:
    - Kryptografische Signaturen für Authentizität und Integrität
    - Standardisiertes Datenmodell und Formate
    - Datenschutzfreundliche Präsentationsmechanismen
    - Interoperable Verifizierungsprotokolle

**In diesem Guide erstellen wir speziell einen Issuer für Verifiable Credentials**, der
dem W3C-Standard folgt, nicht nur ein beliebiges System für
[digitale Nachweise](https://www.corbado.com/de/blog/digital-credentials-api). Das von uns verwendete
[OpenID4VCI](https://www.corbado.com/glossary/openid4vci)-Protokoll ist speziell für die Ausstellung von
Verifiable Credentials konzipiert, und das JWT-VC-Format, das wir implementieren werden,
ist ein W3C-konformes Format für [Verifiable Credentials](https://www.corbado.com/glossary/microcredentials).

### 1.2 Wie es funktioniert

Die Magie hinter digitalen Nachweisen liegt in einem einfachen, aber leistungsstarken
**„Vertrauensdreieck“-Modell** mit drei Hauptakteuren:

- **Issuer:** Eine vertrauenswürdige Instanz (z. B. eine
  [Regierungsbehörde](https://www.corbado.com/passkeys-for-public-sector), Universität oder Bank), die einen
  Nachweis kryptografisch signiert und an einen Nutzer ausstellt. **Das ist die Rolle, die
  wir in diesem Guide erstellen.**
- **Holder:** Der Nutzer, der den Nachweis erhält und ihn sicher in einer persönlichen
  digitalen [Wallet](https://www.corbado.com/blog/digital-wallet-assurance) auf seinem Gerät speichert.
- **Verifier:** Eine Anwendung oder ein Dienst, der den Nachweis des Nutzers überprüfen
  muss.

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

Der Ausstellungsprozess ist der erste Schritt in diesem Ökosystem. Der
[Issuer](https://www.corbado.com/glossary/issuer) validiert die Informationen des Nutzers und stellt ihm einen
Nachweis zur Verfügung. Sobald der Holder diesen Nachweis in seiner
[Wallet](https://www.corbado.com/blog/digital-wallet-assurance) hat, kann er ihn einem Verifier vorlegen, um
seine [Identität](https://www.corbado.com/de/blog/digital-credentials-api) oder Ansprüche nachzuweisen, was das
Dreieck schließt.

Hier ist ein kurzer Blick auf die fertige Anwendung in Aktion:

**Schritt 1: Eingabe der Nutzerdaten** Der Nutzer füllt ein Formular mit seinen
persönlichen Informationen aus, um einen neuen Nachweis anzufordern.
![Eingabeformular für Nutzerdaten](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/issuer_step_1_0733a9e1da.png)

**Schritt 2: Erstellung des Credential-Angebots** Die Anwendung generiert ein sicheres
Credential-Angebot, das als QR-Code und Pre-Authorized-Code angezeigt wird.
![QR-Code für das Credential-Angebot](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/issuer_step_2_3f1881c473.png)

**Schritt 3: Interaktion mit der Wallet** Der Nutzer scannt den QR-Code mit einer
kompatiblen Wallet (z. B. Sphereon Wallet) und gibt eine PIN ein, um die Ausstellung zu
autorisieren.
![Credential-Angebot in der Wallet](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/issuer_step_3_b80d689dfe.png)
![Eingabe des PIN-Codes](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/issuer_step_4_ca8bad8d11.png)

**Schritt 4: Nachweis ausgestellt** Die Wallet empfängt und speichert den neu
ausgestellten digitalen Nachweis, bereit für die zukünftige Verwendung.
![Bestätigung der Nachweisdetails](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/issuer_step_5_55b8150597.png)
![Nachweis hinzugefügt](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/issuer_step_6_7f5ac5745d.png)

## 2. Voraussetzungen für die Erstellung eines Issuers

Bevor wir uns den Code ansehen, gehen wir die Grundlagen und Tools durch, die wir
benötigen. Dieser Guide setzt grundlegende Kenntnisse in der Webentwicklung voraus, aber
die folgenden Voraussetzungen sind für die Erstellung eines Credential-Issuers
unerlässlich.

### 2.1 Wahl der Protokolle

Unser Issuer basiert auf einer Reihe offener Standards, die die Interoperabilität zwischen
Wallets und Ausstellungsdiensten gewährleisten. Für dieses Tutorial konzentrieren wir uns
auf Folgendes:

| Standard / Protokoll                                              | Beschreibung                                                                                                                                                                                                                                                                 |
| :---------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **OpenID4VCI**                                                    | **OpenID for Verifiable Credential Issuance.** Dies ist das Kernprotokoll, das wir verwenden werden. Es definiert einen Standardablauf, wie ein Nutzer (über seine Wallet) einen Nachweis von einem Issuer anfordern und erhalten kann.                                      |
| **[JWT-VC](https://www.w3.org/TR/vc-data-model/#json-web-token)** | **JWT-basierte Verifiable Credentials.** Das Format für den Nachweis, den wir ausstellen werden. Es ist ein W3C-Standard, der Verifiable Credentials als JSON Web Tokens (JWTs) kodiert, was sie kompakt und webfreundlich macht.                                            |
| **[ISO mDoc](https://www.iso.org/standard/69084.html)**           | **ISO/IEC 18013-5.** Der internationale Standard für mobile Führerscheine (mDLs). Obwohl wir ein JWT-VC ausstellen, sind die _Claims_ darin so strukturiert, dass sie mit dem mDoc-Datenmodell kompatibel sind (z. B. `eu.europa.ec.eudi.pid.1`).                            |
| **OAuth 2.0**                                                     | Das zugrunde liegende Autorisierungs-Framework, das von OpenID4VCI verwendet wird. Wir werden einen `pre-authorized_code`-Flow implementieren, der ein spezifischer Grant-Typ ist, der für eine sichere und benutzerfreundliche Ausstellung von Nachweisen entwickelt wurde. |

#### 2.1.1 Autorisierungs-Flows: Pre-Authorized vs. Authorization Code

[OpenID4VCI](https://www.corbado.com/glossary/openid4vci) unterstützt zwei primäre Autorisierungs-Flows für die
Ausstellung von Nachweisen:

1. **Pre-Authorized Code Flow:** In diesem Flow generiert der Issuer einen kurzlebigen
   Einmal-Code (`pre-authorized_code`), der dem Nutzer sofort zur Verfügung steht. Die
   Wallet des Nutzers kann diesen Code dann direkt gegen einen Nachweis eintauschen.
   Dieser Flow ist ideal für Szenarien, in denen der Nutzer bereits authentifiziert und
   auf der Website des Issuers präsent ist, da er eine nahtlose, sofortige Ausstellung
   ohne Umleitungen ermöglicht.

2. **Authorization Code Flow:** Dies ist der Standard-[OAuth 2.0](https://www.corbado.com/glossary/oauth2)-Flow,
   bei dem der Nutzer zu einem Autorisierungsserver weitergeleitet wird, um seine
   Zustimmung zu erteilen. Nach der Genehmigung sendet der Server einen
   `authorization_code` an eine registrierte `redirect_uri` zurück. Dieser Flow eignet
   sich besser für Drittanwendungen, die den Ausstellungsprozess im Namen des Nutzers
   initiieren.

**Für dieses Tutorial verwenden wir den `pre-authorized_code`-Flow.** Wir haben diesen
Ansatz gewählt, weil er einfacher ist und eine direktere Benutzererfahrung für unseren
spezifischen Anwendungsfall bietet: Ein Nutzer fordert direkt einen Nachweis von der
eigenen Website des Issuers an. Er eliminiert die Notwendigkeit komplexer Umleitungen und
Client-Registrierungen, was die Kernlogik der Ausstellung einfacher zu verstehen und zu
implementieren macht.

Diese Kombination von Standards ermöglicht es uns, einen Issuer zu erstellen, der mit
einer Vielzahl von digitalen Wallets kompatibel ist und einen sicheren, standardisierten
Prozess für den Nutzer gewährleistet.

### 2.2 Wahl des Tech-Stacks

Um unseren Issuer zu erstellen, verwenden wir denselben robusten und modernen Tech-Stack,
den wir auch für den Verifier verwendet haben, um eine konsistente und hochwertige
Entwicklererfahrung zu gewährleisten.

#### 2.2.1 Sprache: TypeScript

Wir werden **TypeScript** sowohl für unseren Frontend- als auch für unseren Backend-Code
verwenden. Seine statische Typisierung ist in einer sicherheitskritischen Anwendung wie
einem Issuer von unschätzbarem Wert, da sie hilft, häufige Fehler zu vermeiden und die
allgemeine Qualität und Wartbarkeit des Codes zu verbessern.

#### 2.2.2 Framework: Next.js

**Next.js** ist unser bevorzugtes Framework, da es eine nahtlose, integrierte Erfahrung
für die Erstellung von Full-Stack-Anwendungen bietet.

- **Für das Frontend:** Wir verwenden [Next.js](https://www.corbado.com/blog/nextjs-passkeys) mit
  [React](https://www.corbado.com/blog/react-passkeys), um die Benutzeroberfläche zu erstellen, auf der Nutzer
  ihre Daten eingeben können, um einen Nachweis anzufordern.
- **Für das Backend:** Wir nutzen **Next.js API Routes**, um die serverseitigen Endpunkte
  zu erstellen, die den [OpenID4VCI](https://www.corbado.com/glossary/openid4vci)-Flow abwickeln, von der
  Generierung von Credential-Angeboten bis zur Ausstellung des endgültigen signierten
  Nachweises.

#### 2.2.3 Wichtige Bibliotheken

Unsere Implementierung wird auf einige wichtige Bibliotheken zurückgreifen, um spezifische
Aufgaben zu erledigen:

- **next**, **react** und **react-dom**: Die Kernbibliotheken für unsere
  [Next.js](https://www.corbado.com/blog/nextjs-passkeys)-Anwendung.
- **mysql2**: Ein [MySQL](https://www.corbado.com/blog/passkey-webauthn-database-guide)-Client für
  [Node.js](https://www.corbado.com/blog/nodejs-passkeys), der zur Speicherung von Autorisierungscodes und
  Sitzungsdaten verwendet wird.
- **uuid**: Eine Bibliothek zur Erzeugung eindeutiger Identifikatoren, die wir zur
  Erstellung von `pre-authorized_code`-Werten verwenden werden.
- **jose**: Eine robuste Bibliothek zur Handhabung von JSON Web Signatures (JWS), die wir
  verwenden werden, um die von uns ausgestellten Nachweise kryptografisch zu signieren.

### 2.3 Eine Test-Wallet besorgen

Um Ihren Issuer zu testen, benötigen Sie eine mobile Wallet, die das OpenID4VCI-Protokoll
unterstützt. Für dieses Tutorial empfehlen wir die **Sphereon Wallet**, die sowohl für
[Android](https://www.corbado.com/blog/how-to-enable-passkeys-android) als auch für
[iOS](https://www.corbado.com/blog/how-to-enable-passkeys-ios) verfügbar ist.

**So installieren Sie die Sphereon Wallet:**

1. **Laden Sie die Wallet** aus dem
   [Google Play Store](https://play.google.com/store/apps/details?id=com.sphereon.ssi.wallet)
   oder dem [Apple App Store](https://apps.apple.com/us/app/sphereon-wallet/id1661096796)
   herunter.
2. Installieren Sie die App auf Ihrem Mobilgerät.
3. Nach der Installation ist die Wallet bereit, Credential-Angebote durch Scannen eines
   QR-Codes zu empfangen.

### 2.4 Kryptografie-Kenntnisse

Die Ausstellung eines Nachweises ist ein sicherheitskritischer Vorgang, der auf
grundlegenden kryptografischen Konzepten beruht, um Vertrauen und Authentizität zu
gewährleisten.

#### 2.4.1 Digitale Signaturen

Im Kern ist ein [Verifiable Credential](https://www.corbado.com/glossary/verifiable-credential) eine Reihe von
Behauptungen (Claims), die vom Issuer **digital signiert** wurden. Diese Signatur bietet
zwei Garantien:

- **Authentizität:** Sie beweist, dass der Nachweis von einem legitimen Issuer erstellt
  wurde.
- **Integrität:** Sie beweist, dass der Nachweis seit seiner Ausstellung nicht manipuliert
  wurde.

#### 2.4.2 Public-Key-Kryptografie

Digitale Signaturen werden mittels Public-Key-Kryptografie erstellt. So funktioniert es:

1. **Der Issuer besitzt ein Schlüsselpaar:** einen **privaten Schlüssel**, der geheim und
   sicher aufbewahrt wird, und einen entsprechenden **öffentlichen Schlüssel**, der
   öffentlich zugänglich gemacht wird.
2. **Signieren:** Wenn der Issuer einen Nachweis erstellt, verwendet er seinen **privaten
   Schlüssel**, um eine eindeutige digitale Signatur für die Nachweisdaten zu erzeugen.
3. **Verifizierung:** Ein Verifier kann später den **öffentlichen Schlüssel** des Issuers
   verwenden, um die Signatur zu überprüfen. Wenn die Prüfung erfolgreich ist, weiß der
   Verifier, dass der Nachweis authentisch ist und nicht verändert wurde.

In unserer Implementierung generieren wir ein Elliptic Curve (EC)-Schlüsselpaar und
verwenden den `ES256`-Algorithmus, um das JWT-VC zu signieren. Der öffentliche Schlüssel
ist in der DID (`did:web`) des Issuers eingebettet, sodass jeder Verifier ihn entdecken
und die Signatur des Nachweises validieren kann. **Hinweis:** Der `aud` (Audience)-Claim
wird in unseren JWTs absichtlich weggelassen, da der Nachweis für allgemeine Zwecke
konzipiert ist und nicht an eine bestimmte Wallet gebunden sein soll. Wenn Sie die Nutzung
auf eine bestimmte Zielgruppe beschränken möchten, fügen Sie einen `aud`-Claim hinzu und
setzen Sie ihn entsprechend.

## 3. Architekturübersicht

Unsere Issuer-Anwendung ist als Full-Stack-Next.js-Projekt aufgebaut, mit einer klaren
Trennung zwischen Frontend- und Backend-Logik. Diese Architektur ermöglicht es uns, eine
nahtlose Benutzererfahrung zu schaffen, während alle sicherheitskritischen Operationen auf
dem Server abgewickelt werden. **Wichtig:** Die enthaltenen Tabellen
`verification_sessions` und `verified_credentials` im SQL sind für diesen Issuer nicht
erforderlich, werden aber der Vollständigkeit halber aufgeführt.

- **Frontend (`src/app/issue/page.tsx`):** Eine einzelne
  [React](https://www.corbado.com/blog/react-passkeys)-Seite, auf der Nutzer ihre Daten eingeben können, um einen
  Nachweis anzufordern. Sie macht API-Aufrufe an unser Backend, um den Ausstellungsprozess
  zu initiieren.
- **Backend API Routes (`src/app/api/issue/...`):** Eine Reihe von serverseitigen
  Endpunkten, die das OpenID4VCI-Protokoll implementieren.
    - `/.well-known/openid-credential-issuer`: Ein öffentlicher Metadaten-Endpunkt. Dies
      ist die erste URL, die eine Wallet überprüft, um die Fähigkeiten des Issuers zu
      entdecken, einschließlich seines Autorisierungsservers, Token-Endpunkts,
      Credential-Endpunkts und der Arten von Nachweisen, die er anbietet.
    - `/.well-known/openid-configuration`: Ein Standard-OpenID-Connect-Discovery-Endpunkt.
      Obwohl eng mit dem obigen verwandt, dient dieser Endpunkt einer breiteren
      OIDC-bezogenen Konfiguration und ist oft für die Interoperabilität mit
      Standard-OpenID-Clients erforderlich.
    - `/.well-known/did.json`: Das DID-Dokument für unseren Issuer. Bei Verwendung der
      `did:web`-Methode wird diese Datei verwendet, um die öffentlichen Schlüssel des
      Issuers zu veröffentlichen, die Verifier zur Validierung der Signaturen der von ihm
      ausgestellten Nachweise verwenden können.
    - `authorize/route.ts`: Erstellt einen `pre-authorized_code` und ein
      Credential-Angebot.
    - `token/route.ts`: Tauscht den `pre-authorized_code` gegen einen
      [Access Token](https://www.corbado.com/glossary/access-token) ein.
    - `credential/route.ts`: Stellt das endgültige, kryptografisch signierte JWT-VC aus.
    - `schemas/pid/route.ts`: Stellt das JSON-Schema für den PID-Nachweis bereit. Dies
      ermöglicht es jedem Konsumenten des Nachweises, dessen Struktur und Datentypen zu
      verstehen.
- **Bibliothek (`src/lib/`):**
    - `database.ts`: Verwaltet alle Datenbankinteraktionen, wie das Speichern von
      Autorisierungscodes und Issuer-Schlüsseln.
    - `crypto.ts`: Handhabt alle kryptografischen Operationen, einschließlich der
      Schlüsselgenerierung und JWT-Signierung.

Hier ist ein Diagramm, das den Ausstellungsprozess veranschaulicht:

![Ablauf der Ausstellung digitaler Nachweise](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. Den Issuer erstellen

Nachdem wir nun ein solides Verständnis der Standards, Protokolle und Architektur haben,
können wir mit der Erstellung unseres Issuers beginnen.

> **Mitmachen oder den fertigen Code verwenden**
>
> Wir werden nun Schritt für Schritt durch die Einrichtung und Code-Implementierung gehen.
> Wenn Sie lieber direkt zum fertigen Produkt springen möchten, können Sie das
> vollständige Projekt aus unserem GitHub-Repository klonen und lokal ausführen.
>
> ```bash
> git clone https://github.com/corbado/digital-credentials-example.git
> ```

### 4.1 Projekt einrichten

Zuerst initialisieren wir ein neues Next.js-Projekt, installieren die notwendigen
Abhängigkeiten und starten unsere Datenbank.

#### 4.1.1 Initialisieren der Next.js-App

Öffnen Sie Ihr Terminal, navigieren Sie zu dem Verzeichnis, in dem Sie Ihr Projekt
erstellen möchten, und führen Sie den folgenden Befehl aus. Wir verwenden für dieses
Projekt den App Router, TypeScript und Tailwind CSS.

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

Dieser Befehl erstellt eine neue Next.js-Anwendung in Ihrem aktuellen Verzeichnis.

#### 4.1.2 Abhängigkeiten installieren

Als Nächstes müssen wir die Bibliotheken installieren, die sich um JWTs,
Datenbankverbindungen und die Generierung von UUIDs kümmern.

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

Dieser Befehl installiert:

- `jose`: Zum Signieren und Verifizieren von JSON Web Tokens (JWTs).
- `mysql2`: Den [MySQL](https://www.corbado.com/blog/passkey-webauthn-database-guide)-Client für unsere
  Datenbank.
- `uuid`: Zur Generierung eindeutiger Challenge-Strings.
- `@types/uuid`: TypeScript-Typen für die `uuid`-Bibliothek.

#### 4.1.3 Datenbank starten

Unser Backend benötigt eine [MySQL](https://www.corbado.com/blog/passkey-webauthn-database-guide)-Datenbank, um
Autorisierungscodes, Ausstellungssitzungen und Issuer-Schlüssel zu speichern. Wir haben
eine `docker-compose.yml`-Datei beigefügt, um dies zu vereinfachen.

Wenn Sie das Repository geklont haben, können Sie einfach `docker-compose up -d`
ausführen. Wenn Sie von Grund auf neu bauen, erstellen Sie eine Datei namens
`docker-compose.yml` mit dem folgenden Inhalt:

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

Dieses Docker-Compose-Setup erfordert auch ein SQL-Initialisierungsskript. Erstellen Sie
ein Verzeichnis namens `sql` und darin eine Datei namens `init.sql` mit dem folgenden
Inhalt, um die notwendigen Tabellen sowohl für den Verifier als auch für den Issuer
einzurichten:

```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)
);
```

Sobald beide Dateien vorhanden sind, öffnen Sie Ihr Terminal im Projektstammverzeichnis
und führen Sie aus:

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

Dieser Befehl startet einen MySQL-Container im Hintergrund, der für unsere Anwendung
bereit ist.

### 4.2 Implementierung der Shared Libraries

Bevor wir die API-Endpunkte erstellen, lassen Sie uns die Shared Libraries erstellen, die
die Kern-Geschäftslogik behandeln. Dieser Ansatz hält unsere API-Routen sauber und auf die
Verarbeitung von HTTP-Anfragen konzentriert, während die komplexe Arbeit an diese Module
delegiert wird.

#### 4.2.1 Die Datenbank-Bibliothek (`src/lib/database.ts`)

Diese Datei ist die alleinige Quelle der Wahrheit für alle Datenbankinteraktionen. Sie
verwendet die `mysql2`-Bibliothek, um sich mit unserem MySQL-Container zu verbinden, und
stellt eine Reihe von exportierten Funktionen zum Erstellen, Lesen und Aktualisieren von
Datensätzen in unseren Tabellen bereit. Diese Abstraktionsschicht macht unseren Code
modularer und einfacher zu warten.

Erstellen Sie die Datei `src/lib/database.ts` mit dem folgenden Inhalt:

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

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

let connection: mysql.Connection | null = null;

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

// Data-Access-Object (DAO)-Funktionen für jede Tabelle
// ... (z. B. createChallenge, getChallenge, createAuthorizationCode, etc.)
```

> **Hinweis:** Aus Gründen der Kürze wurde die vollständige Liste der DAO-Funktionen
> weggelassen. Den vollständigen Code finden Sie im
> [Projekt-Repository](https://github.com/corbado/digital-credentials-example/blob/main/src/lib/database.ts).
> Diese Datei enthält Funktionen zur Verwaltung von Challenges, Verifizierungssitzungen,
> Autorisierungscodes, Ausstellungssitzungen und Issuer-Schlüsseln.

#### 4.2.2 Die Krypto-Bibliothek (`src/lib/crypto.ts`)

Diese Datei behandelt alle sicherheitskritischen kryptografischen Operationen. Sie
verwendet die `jose`-Bibliothek, um Schlüsselpaare zu generieren und JSON Web Tokens
(JWTs) zu signieren.

**Schlüsselgenerierung** Die Funktion `generateIssuerKeyPair` erstellt ein neues
Elliptic-Curve-Schlüsselpaar, das zum Signieren von Nachweisen verwendet wird. Der
öffentliche Schlüssel wird im JSON Web Key (JWK)-Format exportiert, sodass er in unserem
`did.json`-Dokument veröffentlicht werden kann.

```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; // Eindeutige Schlüssel-ID zuweisen

    // ... (Export des privaten Schlüssels und weitere Einrichtung)

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

**Erstellung von JWT-Credentials** Die Funktion `createJWTVerifiableCredential` ist der
Kern des Ausstellungsprozesses. Sie nimmt die Claims des Nutzers, das Schlüsselpaar des
Issuers und andere Metadaten und erstellt daraus ein signiertes JWT-VC.

```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 = {
        // Die DID des Issuers
        iss: issuerKeyPair.issuerDid,
        // Die DID des Subjekts (Holders)
        sub: subjectId,
        // Zeitpunkt der Ausstellung (iat) und des Ablaufs (exp) des Nachweises
        iat: now,
        exp: now + oneYear,
        // Das Datenmodell für Verifiable Credentials
        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,
            },
        },
    };

    // Signieren des Payloads mit dem privaten Schlüssel des Issuers
    return await new SignJWT(vcPayload)
        .setProtectedHeader({
            alg: issuerKeyPair.algorithm,
            kid: issuerKeyPair.keyId,
            typ: "JWT",
        })
        .sign(issuerKeyPair.privateKey);
}
```

Diese Funktion erstellt den JWT-Payload gemäß dem W3C Verifiable Credentials Data Model
und signiert ihn mit dem privaten Schlüssel des Issuers, wodurch ein sicherer und
verifizierbarer Nachweis entsteht.

### 4.2 Architekturübersicht der Next.js-App

Unsere Next.js-Anwendung ist so strukturiert, dass die Belange zwischen Frontend und
Backend getrennt sind, obwohl sie Teil desselben Projekts sind. Dies wird erreicht, indem
der App Router sowohl für UI-Seiten als auch für API-Endpunkte genutzt wird.

- **Frontend (`src/app/issue/page.tsx`):** Eine einzelne
  [React](https://www.corbado.com/blog/react-passkeys)-Seitenkomponente, die die Benutzeroberfläche für die
  `/issue`-Route definiert. Sie verarbeitet Benutzereingaben und kommuniziert mit unserer
  Backend-API.

- **Backend API Routes (`src/app/api/...`):**
    - **Discovery (`.well-known/.../route.ts`):** Diese Routen stellen öffentliche
      Metadaten-Endpunkte bereit, die es Wallets und anderen Clients ermöglichen, die
      Fähigkeiten und öffentlichen Schlüssel des Issuers zu entdecken.
    - **Issuance (`issue/.../route.ts`):** Diese Endpunkte implementieren die Kernlogik
      von OpenID4VCI, einschließlich der Erstellung von Credential-Angeboten, der
      Ausstellung von Tokens und der Signierung des endgültigen Nachweises.
    - **Schema (`schemas/pid/route.ts`):** Diese Route liefert das JSON-Schema für den
      Nachweis und definiert dessen Struktur.

- **Library (`src/lib/`):** Dieses Verzeichnis enthält wiederverwendbare Logik, die im
  gesamten Backend geteilt wird.
    - `database.ts`: Verwaltet alle Datenbankinteraktionen und abstrahiert SQL-Abfragen.
    - `crypto.ts`: Handhabt alle kryptografischen Operationen, wie z. B. die
      Schlüsselgenerierung und JWT-Signierung.

Diese klare Trennung macht die Anwendung modular und einfacher zu warten.

**Hinweis:** Die Funktion `generateIssuerDid()` muss eine gültige `did:web` zurückgeben,
die Ihrer Issuer-Domäne entspricht. Bei der Bereitstellung muss die Datei
`.well-known/did.json` über HTTPS auf dieser Domäne bereitgestellt werden, damit Verifier
die Nachweise validieren können.

![Architekturübersicht der Next.js-App](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/Mermaid_Chart_Create_complex_visual_diagrams_with_text_A_smarter_way_of_creating_diagrams_2025_07_29_151549_6a0aca6477.svg)

### 4.3 Erstellen des Frontends

Unser Frontend ist eine einzelne React-Seite, die ein einfaches Formular für Benutzer
bereitstellt, um einen neuen digitalen Nachweis anzufordern. Seine Aufgaben sind:

- Benutzerdaten erfassen (Name, Geburtsdatum usw.).
- Diese Daten an unser Backend senden, um ein Credential-Angebot zu erstellen.
- Den resultierenden QR-Code und die PIN anzeigen, damit der Benutzer sie mit seiner
  Wallet scannen kann.

Die Kernlogik wird in der `handleSubmit`-Funktion behandelt, die ausgelöst wird, wenn der
Benutzer das Formular abschickt.

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

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

    try {
        // 1. Erforderliche Felder validieren
        if (!userData.given_name || !userData.family_name || !userData.birth_date) {
            throw new Error("Bitte füllen Sie alle erforderlichen Felder aus");
        }

        // 2. Ein Credential-Angebot vom Backend anfordern
        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 ||
                    "Fehler beim Erstellen des Credential-Angebots",
            );
        }

        // 3. Das Credential-Angebot im State setzen, um den QR-Code anzuzeigen
        const result = await response.json();
        setCredentialOffer(result);
    } catch (err) {
        const errorMessage = (err as Error).message || "Unbekannter Fehler aufgetreten";
        setError(errorMessage);
    } finally {
        setLoading(false);
    }
};
```

Diese Funktion führt drei wesentliche Aktionen aus:

1. **Validiert die Formulardaten**, um sicherzustellen, dass alle erforderlichen Felder
   ausgefüllt sind.
2. **Sendet eine `POST`-Anfrage** an unseren `/api/issue/authorize`-Endpunkt mit den Daten
   des Benutzers.
3. **Aktualisiert den Zustand der Komponente** mit dem vom Backend erhaltenen
   Credential-Angebot, was die Benutzeroberfläche veranlasst, den QR-Code und den
   Transaktionscode anzuzeigen.

Der Rest der Datei enthält Standard-React-Code zum Rendern des Formulars und der
QR-Code-Anzeige. Sie können die vollständige Datei im
[Projekt-Repository](https://github.com/corbado/digital-credentials-example/blob/main/src/app/issue/page.tsx)
einsehen.

### 4.4 Einrichtung der Umgebung und Discovery

Bevor wir die Backend-API erstellen, müssen wir unsere Umgebung konfigurieren und die
Discovery-Endpunkte einrichten. Diese `.well-known`-Dateien sind entscheidend, damit
Wallets unseren Issuer finden und verstehen können, wie sie mit ihm interagieren können.

#### 4.4.1 Erstellen der Umgebungsdatei

Erstellen Sie eine Datei namens `.env.local` im Stammverzeichnis Ihres Projekts und fügen
Sie die folgende Zeile hinzu. Diese URL muss öffentlich zugänglich sein, damit eine mobile
Wallet sie erreichen kann. Für die lokale Entwicklung können Sie einen Tunneling-Dienst
wie [ngrok](https://www.corbado.com/blog/multi-device-passkey-login-corbado-ngrok) verwenden, um Ihren
`localhost` freizugeben.

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

#### 4.4.2 Implementierung der Discovery-Endpunkte

Wallets entdecken die Fähigkeiten eines Issuers, indem sie Standard-`.well-known`-URLs
abfragen. Wir müssen drei dieser Endpunkte erstellen.

**1. Issuer-Metadaten (`/.well-known/openid-credential-issuer`)**

Dies ist die primäre Discovery-Datei für OpenID4VCI. Sie teilt der Wallet alles mit, was
sie über den Issuer wissen muss, einschließlich seiner Endpunkte, der Arten von
Nachweisen, die er anbietet, und der unterstützten kryptografischen Algorithmen.

Erstellen Sie die Datei `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 = {
        // Der eindeutige Bezeichner des Issuers.
        issuer: baseUrl,
        // Die URL des Autorisierungsservers. Der Einfachheit halber ist unser Issuer sein eigener Autorisierungsserver.
        authorization_servers: [baseUrl],
        // Die URL des Credential-Issuers.
        credential_issuer: baseUrl,
        // Der Endpunkt, an den die Wallet POST-Anfragen sendet, um den eigentlichen Nachweis zu erhalten.
        credential_endpoint: `${baseUrl}/api/issue/credential`,
        // Der Endpunkt, an dem die Wallet einen Autorisierungscode gegen einen Access Token austauscht.
        token_endpoint: `${baseUrl}/api/issue/token`,
        // Der Endpunkt für den Autorisierungs-Flow (wird in unserem Pre-Authorized-Flow nicht verwendet, aber es ist gute Praxis, ihn anzugeben).
        authorization_endpoint: `${baseUrl}/api/issue/authorize`,
        // Zeigt die Unterstützung für den Pre-Authorized-Code-Flow ohne Client-Authentifizierung an.
        pre_authorized_grant_anonymous_access_supported: true,
        // Lesbare Informationen über den Issuer.
        display: [
            {
                name: "Corbado Credentials Issuer",
                locale: "en-US",
            },
        ],
        // Eine Liste der Nachweistypen, die dieser Issuer ausstellen kann.
        credential_configurations_supported: {
            "eu.europa.ec.eudi.pid.1": {
                // Das Format des Nachweises (z. B. jwt_vc, mso_mdoc).
                format: "jwt_vc",
                // Der spezifische Dokumententyp, der den ISO-mDoc-Standards entspricht.
                doctype: "eu.europa.ec.eudi.pid.1",
                // Der mit diesem Nachweistyp verbundene OAuth 2.0-Scope.
                scope: "eu.europa.ec.eudi.pid.1",
                // Methoden, die die Wallet verwenden kann, um den Besitz ihres Schlüssels nachzuweisen.
                cryptographic_binding_methods_supported: ["jwk"],
                // Signaturalgorithmen, die der Issuer für diesen Nachweis unterstützt.
                credential_signing_alg_values_supported: ["ES256"],
                // Proof-of-Possession-Typen, die die Wallet verwenden kann.
                proof_types_supported: {
                    jwt: {
                        proof_signing_alg_values_supported: ["ES256", "ES384", "ES512"],
                    },
                },
                // Anzeige-Eigenschaften für den Nachweis.
                display: [
                    {
                        name: "Corbado Credential Issuer",
                        locale: "en-US",
                        logo: {
                            uri: `${baseUrl}/logo.png`,
                            alt_text: "EU Digital Identity",
                        },
                        background_color: "#003399",
                        text_color: "#FFFFFF",
                    },
                ],
                // Eine Liste der Claims (Attribute) im Nachweis.
                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" }],
                        },
                    },
                },
            },
        },
        // Authentifizierungsmethoden, die vom Token-Endpunkt unterstützt werden. 'none' bedeutet öffentlicher Client.
        token_endpoint_auth_methods_supported: ["none"],
        // Unterstützte PKCE-Code-Challenge-Methoden.
        code_challenge_methods_supported: ["S256"],
        // OAuth 2.0 Grant-Typen, die der Issuer unterstützt.
        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. OpenID-Konfiguration (`/.well-known/openid-configuration`)**

Dies ist ein Standard-OIDC-Discovery-Dokument, das einen breiteren Satz von
Konfigurationsdetails bereitstellt.

Erstellen Sie die Datei `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 = {
        // Der eindeutige Bezeichner des Issuers.
        credential_issuer: baseUrl,
        // Der Endpunkt, an den die Wallet POST-Anfragen sendet, um den eigentlichen Nachweis zu erhalten.
        credential_endpoint: `${baseUrl}/api/issue/credential`,
        // Der Endpunkt für den Autorisierungs-Flow.
        authorization_endpoint: `${baseUrl}/api/issue/authorize`,
        // Der Endpunkt, an dem die Wallet einen Autorisierungscode gegen einen Access Token austauscht.
        token_endpoint: `${baseUrl}/api/issue/token`,
        // Eine Liste der Nachweistypen, die dieser Issuer ausstellen kann.
        credential_configurations_supported: {
            "eu.europa.ec.eudi.pid.1": {
                format: "jwt_vc",
                scope: "eu.europa.ec.eudi.pid.1",
                cryptographic_binding_methods_supported: ["jwk"],
                credential_signing_alg_values_supported: ["ES256", "ES384", "ES512"],
                proof_types_supported: {
                    jwt: {
                        proof_signing_alg_values_supported: ["ES256", "ES384", "ES512"],
                    },
                },
            },
        },
        // OAuth 2.0 Grant-Typen, die der Issuer unterstützt.
        grant_types_supported: [
            "authorization_code",
            "urn:ietf:params:oauth:grant-type:pre-authorized_code",
        ],
        // Zeigt die Unterstützung für den Pre-Authorized-Code-Flow an.
        pre_authorized_grant_anonymous_access_supported: true,
        // Unterstützte PKCE-Code-Challenge-Methoden.
        code_challenge_methods_supported: ["S256"],
        // Authentifizierungsmethoden, die vom Token-Endpunkt unterstützt werden.
        token_endpoint_auth_methods_supported: ["none"],
        // OAuth 2.0-Scopes, die der Issuer unterstützt.
        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. DID-Dokument (`/.well-known/did.json`)**

Diese Datei veröffentlicht den öffentlichen Schlüssel des Issuers unter Verwendung der
`did:web`-Methode, sodass jeder die Signatur der von ihm ausgestellten Nachweise
überprüfen kann.

Erstellen Sie die Datei `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 = {
        // Der Kontext definiert das im Dokument verwendete Vokabular.
        "@context": [
            "https://www.w3.org/ns/did/v1",
            "https://w3id.org/security/suites/jws-2020/v1",
        ],
        // Der DID-URI, der der eindeutige Bezeichner für den Issuer ist.
        id: didId,
        // Der DID-Controller, also die Entität, die die DID kontrolliert. Hier ist es der Issuer selbst.
        controller: didId,
        // Eine Liste von öffentlichen Schlüsseln, die zur Überprüfung von Signaturen des Issuers verwendet werden können.
        verificationMethod: [
            {
                // Ein eindeutiger Bezeichner für den Schlüssel, bezogen auf die DID.
                id: `${didId}#${issuerKey.key_id}`,
                // Der Typ des Schlüssels.
                type: "JsonWebKey2020",
                // Die DID des Schlüssel-Controllers.
                controller: didId,
                // Der öffentliche Schlüssel im JWK-Format.
                publicKeyJwk: publicKeyJWK,
            },
        ],
        // Gibt an, welche Schlüssel zur Authentifizierung (Nachweis der Kontrolle über die DID) verwendet werden können.
        authentication: [`${didId}#${issuerKey.key_id}`],
        // Gibt an, welche Schlüssel zur Erstellung von Verifiable Credentials verwendet werden können.
        assertionMethod: [`${didId}#${issuerKey.key_id}`],
        // Eine Liste von Diensten, die vom DID-Subjekt bereitgestellt werden, wie z. B. der Issuer-Endpunkt.
        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",
        },
    });
}
```

> **Warum kein Caching?** Sie werden feststellen, dass alle drei dieser Endpunkte Header
> zurückgeben, die das Caching aggressiv verhindern (`Cache-Control: no-cache`,
> `Pragma: no-cache`, `Expires: 0`). Dies ist eine wichtige Sicherheitspraxis für
> Discovery-Dokumente. Issuer-Konfigurationen können sich ändern – zum Beispiel könnte ein
> kryptografischer Schlüssel rotiert werden. Wenn eine Wallet oder ein Client eine alte
> Version der `did.json`- oder `openid-credential-issuer`-Datei zwischenspeichern würde,
> könnte sie neue Nachweise nicht validieren oder mit aktualisierten Endpunkten
> interagieren. Indem wir Clients zwingen, bei jeder Anfrage eine frische Kopie abzurufen,
> stellen wir sicher, dass sie immer die aktuellsten Informationen haben.

#### 4.4.3 Implementierung des Credential-Schema-Endpunkts

Das letzte Stück unserer öffentlich zugänglichen
[Infrastruktur](https://www.corbado.com/passkeys-for-critical-infrastructure) ist der Credential-Schema-Endpunkt.
Diese Route liefert ein JSON-Schema, das die Struktur, Datentypen und Einschränkungen des
von uns ausgestellten PID-Nachweises formal definiert. Wallets und Verifier können dieses
Schema verwenden, um den Inhalt des Nachweises zu validieren.

Erstellen Sie die Datei `src/app/api/schemas/pid/route.ts` mit dem folgenden Inhalt:

```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", // Ersetzen Sie dies durch Ihre tatsächliche Domain
        title: "PID Credential",
        description:
            "A schema for a Verifiable Credential representing a Personal Identification Document (PID).",
        type: "object",
        properties: {
            credentialSubject: {
                type: "object",
                properties: {
                    given_name: { type: "string" },
                    family_name: { type: "string" },
                    birth_date: { type: "string", format: "date" },
                    // ... andere Eigenschaften des Credential-Subjekts
                },
                required: ["given_name", "family_name", "birth_date"],
            },
            // ... andere Top-Level-Eigenschaften eines Verifiable Credentials
        },
    };

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

> **Hinweis:** Das JSON-Schema für einen PID-Nachweis kann ziemlich groß und detailliert
> sein. Aus Gründen der Kürze wurde das vollständige Schema gekürzt. Sie finden die
> vollständige Datei im
> [Projekt-Repository](https://github.com/corbado/digital-credentials-example/blob/main/src/app/api/schemas/pid/route.ts).

### 4.5 Erstellen der Backend-Endpunkte

Mit dem Frontend an Ort und Stelle benötigen wir nun die serverseitige Logik, um den
OpenID4VCI-Flow zu handhaben. Wir beginnen mit dem ersten Endpunkt, den das Frontend
aufruft: `/api/issue/authorize`.

#### 4.5.1 `/api/issue/authorize`: Erstellen des Credential-Angebots

Dieser Endpunkt ist dafür verantwortlich, die Daten des Benutzers entgegenzunehmen, einen
sicheren Einmal-Code zu generieren und ein `credential_offer` zu erstellen, das die Wallet
des Benutzers verstehen kann.

Hier ist die Kernlogik:

```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. Benutzerdaten validieren
        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. Einen Pre-Authorized-Code und eine PIN generieren
        const code = uuidv4();
        const expiresAt = new Date(Date.now() + 10 * 60 * 1000); // 10 Minuten
        const txCode = Math.floor(1000 + Math.random() * 9000).toString(); // 4-stellige PIN

        // 3. Den Code und die Benutzerdaten speichern
        await createAuthorizationCode(uuidv4(), code, expiresAt);
        // Hinweis: Dies verwendet nur zu Demozwecken einen In-Memory-Speicher.
        // In der Produktion sollten Daten sicher in einer Datenbank mit korrektem Ablaufdatum gespeichert werden.
        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. Das Credential-Angebot-Objekt erstellen
        const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000";
        const credentialOffer = {
            // Der Bezeichner des Issuers, also seine Basis-URL.
            credential_issuer: baseUrl,
            // Ein Array von Nachweistypen, die der Issuer anbietet.
            credential_configuration_ids: ["eu.europa.ec.eudi.pid.1"],
            // Gibt die Grant-Typen an, die die Wallet verwenden kann.
            grants: {
                // Wir verwenden den Pre-Authorized-Code-Flow.
                "urn:ietf:params:oauth:grant-type:pre-authorized_code": {
                    // Der Einmal-Code, den die Wallet gegen einen Token eintauschen wird.
                    "pre-authorized_code": code,
                    // Zeigt an, dass der Benutzer eine PIN (tx_code) eingeben muss, um den Code einzulösen.
                    user_pin_required: true,
                },
            },
        };

        // 5. Die vollständige Credential-Offer-URI erstellen (ein Deep-Link für Wallets)
        const credentialOfferUri = `openid-credential-offer://?credential_offer=${encodeURIComponent(
            JSON.stringify(credentialOffer),
        )}`;

        // Die endgültige Antwort an das Frontend.
        return NextResponse.json({
            // Der Deep-Link für den QR-Code.
            credential_offer_uri: credentialOfferUri,
            // Der rohe Pre-Authorized-Code zur Anzeige oder manuellen Eingabe.
            pre_authorized_code: code,
            // Die 4-stellige PIN, die der Benutzer in seiner Wallet eingeben muss.
            tx_code: txCode,
        });
    } catch (error) {
        console.error("Authorization error:", error);
        return NextResponse.json({ error: "server_error" }, { status: 500 });
    }
}
```

Wichtige Schritte in diesem Endpunkt:

1. **Daten validieren:** Zuerst wird sichergestellt, dass die erforderlichen Benutzerdaten
   vorhanden sind.
2. **Codes generieren:** Es wird ein eindeutiger `pre-authorized_code` (eine UUID) und ein
   4-stelliger `tx_code` (PIN) für eine zusätzliche Sicherheitsebene erstellt.
3. **Daten speichern:** Der `pre-authorized_code` wird mit einer kurzen Ablaufzeit in der
   Datenbank gespeichert. Die Daten des Benutzers und die PIN werden im Speicher abgelegt
   und mit dem Code verknüpft.
4. **Angebot erstellen:** Es wird das `credential_offer`-Objekt gemäß der
   OpenID4VCI-Spezifikation erstellt. Dieses Objekt teilt der Wallet mit, wo sich der
   Issuer befindet, welche Nachweise er anbietet und welcher Code benötigt wird, um sie zu
   erhalten.
5. **URI zurückgeben:** Schließlich wird eine Deep-Link-URI
   (`openid-credential-offer://...`) erstellt und zusammen mit dem `tx_code` an das
   Frontend zurückgegeben, damit der Benutzer ihn sehen kann.

#### 4.5.2 `/api/issue/token`: Den Code gegen einen Token austauschen

Sobald der Benutzer den QR-Code scannt und seine PIN eingibt, sendet die Wallet eine
`POST`-Anfrage an diesen Endpunkt. Seine Aufgabe ist es, den `pre-authorized_code` und den
`user_pin` (PIN) zu validieren und, falls sie gültig sind, einen kurzlebigen
[Access Token](https://www.corbado.com/glossary/access-token) auszustellen.

Erstellen Sie die Datei `src/app/api/issue/token/route.ts` mit dem folgenden Inhalt:

```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. Den Grant-Typ validieren
        if (grant_type !== "urn:ietf:params:oauth:grant-type:pre-authorized_code") {
            return NextResponse.json(
                { error: "unsupported_grant_type" },
                { status: 400 },
            );
        }

        // 2. Den Pre-Authorized-Code validieren
        const authCode = await getAuthorizationCode(code);
        if (!authCode) {
            return NextResponse.json(
                {
                    error: "invalid_grant",
                    error_description: "Invalid or expired code",
                },
                { status: 400 },
            );
        }

        // 3. Die PIN (tx_code) validieren
        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. Access Token und c_nonce generieren
        const accessToken = uuidv4();
        const cNonce = uuidv4();
        const cNonceExpiresAt = new Date(Date.now() + 5 * 60 * 1000); // 5 Minuten

        // 5. Eine neue Ausstellungssitzung erstellen
        const userData = (global as any).userDataStore?.get(code);
        await createIssuanceSession(
            uuidv4(),
            authCode.id,
            accessToken,
            cNonce,
            cNonceExpiresAt,
            userData,
        );

        // 6. Den Code als verwendet markieren und temporäre Daten bereinigen
        await markAuthorizationCodeAsUsed(code);
        (global as any).txCodeStore?.delete(code);
        (global as any).userDataStore?.delete(code);

        // 7. Die Access-Token-Antwort zurückgeben
        return NextResponse.json({
            access_token: accessToken,
            token_type: "Bearer",
            expires_in: 3600, // 1 Stunde
            c_nonce: cNonce,
            c_nonce_expires_in: 300, // 5 Minuten
        });
    } catch (error) {
        console.error("Token endpoint error:", error);
        return NextResponse.json({ error: "server_error" }, { status: 500 });
    }
}
```

Wichtige Schritte in diesem Endpunkt:

1. **Grant-Typ validieren:** Es wird sichergestellt, dass die Wallet den korrekten
   `pre-authorized_code`-Grant-Typ verwendet.
2. **Code validieren:** Es wird geprüft, ob der `pre-authorized_code` in der Datenbank
   existiert, nicht abgelaufen ist und noch nicht verwendet wurde.
3. **PIN validieren:** Es wird der `user_pin` aus der Wallet mit dem zuvor gespeicherten
   `tx_code` verglichen, um sicherzustellen, dass der Benutzer die Transaktion autorisiert
   hat.
4. **Tokens generieren:** Es werden ein sicherer `access_token` und eine `c_nonce`
   (Credential Nonce) erstellt, die ein einmaliger Wert ist, um Replay-Angriffe auf den
   Credential-Endpunkt zu verhindern.
5. **Sitzung erstellen:** Es wird ein neuer `issuance_sessions`-Eintrag in der Datenbank
   erstellt, der den [Access Token](https://www.corbado.com/glossary/access-token) mit den Daten des Benutzers
   verknüpft.
6. **Code als verwendet markieren:** Um zu verhindern, dass dasselbe Angebot zweimal
   verwendet wird, wird der `pre-authorized_code` als verwendet markiert.
7. **Token zurückgeben:** Es werden der `access_token` und die `c_nonce` an die Wallet
   zurückgegeben.

#### 4.5.3 `/api/issue/credential`: Den signierten Nachweis ausstellen

Dies ist der letzte und wichtigste Endpunkt. Die Wallet verwendet den Access Token, den
sie vom `/token`-Endpunkt erhalten hat, um eine authentifizierte `POST`-Anfrage an diese
Route zu senden. Die Aufgabe dieses Endpunkts ist es, die endgültige Validierung
durchzuführen, den kryptografisch signierten Nachweis zu erstellen und ihn an die Wallet
zurückzugeben.

Erstellen Sie die Datei `src/app/api/issue/credential/route.ts` mit dem folgenden Inhalt:

```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. Den Bearer-Token validieren
        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. Die Benutzerdaten aus der Sitzung abrufen
        const userData = session.user_data;
        if (!userData) {
            return NextResponse.json({ error: "missing_user_data" }, { status: 400 });
        }

        // 3. Den aktiven Issuer-Schlüssel abrufen
        const issuerKey = await getActiveIssuerKey();
        if (!issuerKey) {
            // In einer echten Anwendung hätten Sie ein robusteres Schlüsselverwaltungssystem.
            // Für diese Demo können wir einen Schlüssel on-the-fly generieren, wenn keiner existiert.
            // Dieser Teil wurde aus Gründen der Kürze weggelassen, befindet sich aber im Repository.
            return NextResponse.json(
                {
                    error: "server_error",
                    error_description: "Failed to get issuer key",
                },
                { status: 500 },
            );
        }

        // 4. Den JWT-VC erstellen
        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. Den ausgestellten Nachweis in der Datenbank speichern
        await createIssuedCredential(/* ... Nachweisdetails ... */);
        await updateIssuanceSession(session.id, "credential_issued");

        // 6. Den signierten Nachweis zurückgeben
        return NextResponse.json({
            format: "jwt_vc",
            credential: credentialData,
            c_nonce: uuidv4(), // Eine neue Nonce für nachfolgende Anfragen
            c_nonce_expires_in: 300,
        });
    } catch (error) {
        console.error("Credential endpoint error:", error);
        return NextResponse.json({ error: "server_error" }, { status: 500 });
    }
}
```

Wichtige Schritte in diesem Endpunkt:

1. **Token validieren:** Es wird nach einem gültigen `Bearer`-Token im
   `Authorization`-Header gesucht und damit die aktive Ausstellungssitzung nachgeschlagen.
2. **Benutzerdaten abrufen:** Es werden die Claims-Daten des Benutzers abgerufen, die in
   der Sitzung gespeichert wurden, als der Token erstellt wurde.
3. **Issuer-Schlüssel laden:** Es wird der aktive Signierschlüssel des Issuers aus der
   Datenbank geladen. In einem realen Szenario würde dies von einem sicheren
   Schlüsselverwaltungssystem übernommen.
4. **Nachweis erstellen:** Es wird unser Helfer `createJWTVerifiableCredential` aus
   `src/lib/crypto.ts` aufgerufen, um den JWT-VC zu erstellen und zu signieren.
5. **Ausstellung protokollieren:** Es wird ein Eintrag des ausgestellten Nachweises in der
   Datenbank für Audit- und Widerrufszwecke gespeichert.
6. **Nachweis zurückgeben:** Es wird der signierte Nachweis in einer JSON-Antwort an die
   Wallet zurückgegeben. Die Wallet ist dann für die sichere Speicherung verantwortlich.

## 5. Den Issuer ausführen und nächste Schritte

Sie haben jetzt eine vollständige End-to-End-Implementierung eines Issuers für digitale
Nachweise. Hier erfahren Sie, wie Sie ihn lokal ausführen und was Sie beachten müssen, um
ihn von einem Proof-of-Concept zu einer produktionsreifen Anwendung zu machen.

### 5.1 Wie man das Beispiel ausführt

1. **Das Repository klonen:**

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

2. **Abhängigkeiten installieren:**

    ```bash
    npm install
    ```

3. **Die Datenbank starten:** Stellen Sie sicher, dass Docker läuft, und starten Sie dann
   den MySQL-Container:

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

4. **Umgebung konfigurieren & Tunnel starten:** Dies ist der kritischste Schritt für
   lokale Tests. Da Ihre mobile Wallet sich über das Internet mit Ihrer
   Entwicklungsmaschine verbinden muss, müssen Sie Ihren lokalen Server mit einer
   öffentlichen HTTPS-URL freigeben. Wir verwenden dafür `ngrok`.

    a. **ngrok starten:**

    ```bash
    ngrok http 3000
    ```

    b. **Kopieren Sie die HTTPS-URL** aus der
    [ngrok](https://www.corbado.com/blog/multi-device-passkey-login-corbado-ngrok)-Ausgabe (z. B.
    `https://random-string.ngrok.io`). c. **Erstellen Sie eine `.env.local`-Datei** und
    setzen Sie die URL:

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

5. **Die Anwendung ausführen:**

    ```bash
    npm run dev
    ```

    Öffnen Sie Ihren Browser unter `http://localhost:3000/issue`. Sie können nun das
    Formular ausfüllen, und der generierte QR-Code wird korrekt auf Ihre öffentliche
    [ngrok](https://www.corbado.com/blog/multi-device-passkey-login-corbado-ngrok)-URL verweisen, sodass Ihre
    mobile Wallet sich verbinden und den Nachweis empfangen kann.

### 5.2 Die Bedeutung von HTTPS und `ngrok`

Protokolle für digitale Nachweise werden mit
[Sicherheit](https://www.corbado.com/de/blog/vollstaendig-passwortlos-werden) als oberster Priorität entwickelt.
Aus diesem Grund werden Wallets fast immer die Verbindung zu einem Issuer über eine
unsichere (`http://`) Verbindung ablehnen. Der gesamte Prozess basiert auf einer sicheren
**HTTPS**-Verbindung, die durch ein **SSL-Zertifikat** ermöglicht wird.

Ein Tunneldienst wie `ngrok` löst beide Probleme, indem er eine sichere, öffentlich
zugängliche HTTPS-URL (mit einem gültigen SSL-Zertifikat) erstellt, die den gesamten
Datenverkehr an Ihren lokalen Entwicklungsserver weiterleitet. Wallets erfordern HTTPS und
werden sich weigern, sich mit unsicheren (`http://`) Endpunkten zu verbinden. Dies ist ein
unverzichtbares Werkzeug zum Testen von Webdiensten, die mit mobilen Geräten oder externen
Webhooks interagieren müssen.

### 5.3 Was nicht im Rahmen dieses Tutorials liegt

Dieses Beispiel konzentriert sich bewusst auf den Kern des Ausstellungsprozesses, um es
leicht verständlich zu machen. Die folgenden Themen werden als außerhalb des Rahmens
betrachtet:

- **Produktionsreife Sicherheit:** Der Issuer dient zu Bildungszwecken. Ein
  Produktionssystem würde ein sicheres Schlüsselverwaltungssystem (KMS) anstelle der
  Speicherung von Schlüsseln in einer Datenbank, eine robuste Fehlerbehandlung,
  Ratenbegrenzung und eine umfassende Audit-Protokollierung erfordern.
- **Widerruf von Nachweisen:** Dieser Leitfaden implementiert keinen Mechanismus zum
  Widerrufen von ausgestellten Nachweisen. Obwohl das Schema ein `revoked`-Flag für die
  zukünftige Verwendung enthält, wird hier keine Widerrufslogik bereitgestellt.
- **Authorization Code Flow:** Wir haben uns ausschließlich auf den
  `pre-authorized_code`-Flow konzentriert. Eine vollständige Implementierung des
  `authorization_code`-Flows würde einen Zustimmungsbildschirm für den Benutzer und eine
  komplexere [OAuth 2.0](https://www.corbado.com/glossary/oauth2)-Logik erfordern.
- **Benutzerverwaltung:** Der Leitfaden enthält keine Benutzerauthentifizierung oder
  -verwaltung für den Issuer selbst. Es wird angenommen, dass der Benutzer bereits
  authentifiziert und berechtigt ist, einen Nachweis zu erhalten.

## 6. Fazit

Das war's! Mit ein paar Seiten Code haben wir nun einen vollständigen End-to-End-Issuer
für digitale Nachweise, der:

1. Ein benutzerfreundliches Frontend zur Anforderung von Nachweisen bereitstellt.
2. Den vollständigen OpenID4VCI `pre-authorized_code`-Flow implementiert.
3. Alle notwendigen Discovery-Endpunkte für die Interoperabilität mit Wallets
   bereitstellt.
4. Einen sicheren, standardkonformen
   JWT-[Verifiable Credential](https://www.corbado.com/glossary/verifiable-credential) generiert und signiert.

Obwohl dieser Leitfaden eine solide Grundlage bietet, würde ein produktionsreifer Issuer
zusätzliche Funktionen wie robustes Schlüsselmanagement, persistenten Speicher anstelle
von In-Memory-Speichern, Widerruf von Nachweisen und eine umfassende Sicherheitshärtung
erfordern. Die Kompatibilität mit Wallets variiert ebenfalls; die Sphereon Wallet wird zum
Testen empfohlen, aber andere Wallets unterstützen den hier implementierten
Pre-Authorized-Flow möglicherweise nicht. Die Kernbausteine und der Interaktionsfluss
würden jedoch gleich bleiben. Indem Sie diesen Mustern folgen, können Sie einen sicheren
und interoperablen Issuer für jede Art von digitalem Nachweis erstellen.

## 7. Ressourcen

Hier sind einige der wichtigsten Ressourcen, Spezifikationen und Tools, die in diesem
Tutorial verwendet oder referenziert wurden:

- **Projekt-Repository:**
    - [Vollständiger Quellcode auf GitHub](https://github.com/corbado/digital-credentials-example)

- **Wichtige Spezifikationen:**
    - [OpenID for Verifiable Credential Issuance (OpenID4VCI)](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html):
      Das Kernprotokoll für die Ausstellung.
    - [W3C Verifiable Credentials Data Model](https://www.w3.org/TR/vc-data-model/): Der
      grundlegende Standard für VCs.
    - [Die `did:web`-Methode](https://w3c-ccg.github.io/did-method-web/): Die DID-Methode,
      die für den öffentlichen Schlüssel unseres Issuers verwendet wird.

- **Tools:**
    - [Sphereon Wallet](https://sphereon.com/wallet/): Die in diesem Guide verwendete
      Test-Wallet.
    - ngrok: Zum Erstellen eines sicheren Tunnels zu Ihrer lokalen Entwicklungsumgebung.

- **Bibliotheken:**
    - Next.js: Das React-Framework zum Erstellen des Frontends und Backends.
    - [jose](https://github.com/panva/jose): Zum Erstellen und Signieren von JSON Web
      Tokens (JWTs).
    - [mysql2](https://github.com/sidorares/node-mysql2): Der MySQL-Client für
      [Node.js](https://www.corbado.com/blog/nodejs-passkeys).
