Die e-ID Verifikation erklärt

In diesem Blogbeitrag möchte ich zeigen, wie eine e-ID-Verifizierung implementiert werden kann. Du erfährst, wie du deine Anwendung so einrichten kannst, dass der Verifizierungsablauf funktioniert und die damit verbundenen Daten sicher sind.
Jürg Hunziker

Entwicklung · 29.10.2025

Wie funktioniert die e-ID Verifikation?

Um das Verständnis dieses Blogbeitrags zu erleichtern, verwenden wir als Beispiel der e-ID Verifizierung, die Altersüberprüfung, welche wir für unseren Demo-Shop entwickelt haben. Diesen findest du hier: https://shop.playground.neovo.ch. Und hier findest du den dazugehörigen Blogpost: Die e-ID im Einsatz: Kauf jetzt unser Bier!.

Beteiligte Services und Anwendungen

Zu Beginn möchte ich einen Überblick über alle Services geben, welche für eine e-ID-Verifizierung verwendet werden.

Verifier-Service

Der wichtigste Teil der e-ID-Verifikation ist der Verifier-Service. Er führt den Verifizierungsprozess aus und validiert seine Anfragen gegen die Vertrauens-Infrastruktur des Bundes.

Dieser Service ist dafür gedacht, von der verifizierenden Stelle selbst gehostet zu werden, denn er ist neben dem Client die einzige Komponente, die erfahren soll, welche Person, welche Verifikation durchführen will. Das ist entscheidend, da diese Information für ein mögliches Profiling genutzt werden könnte und daher nicht bei einer fremden Organisation (zB. als SaaS-Plattform) oder beim Bund liegen sollte.

Shop-Backend

Das Shop-Backend bietet einen öffentlichen Endpoint an, welcher den Verifizierungsprozess beim Verifier-Service initialisiert und die nötigen Verifikations-Infomationen zurückgibt. Dieser Endpoint definiert, welche Informationen für die Verifikation benötigt werden. In unserem Demo-Fall ist das ein Boolean, der dem Shop mitteilt, ob der aktuelle Nutzer mindestens 16 Jahre alt ist.

Shop Frontend

Das Frontend ruft den erwähnten Endpoint im Shop-Backend auf, um eine Verifikation zu initialisieren. Anschließend zeigt es den erhaltenen Link als QR-Code oder direkt als Deeplink in die swiyu-App an, abhängig vom Gerät, welches die Verifikation durchführt.

swiyu App

Die Kundin bzw. der Kunde scannt den QR-Code und startet dadurch die Verifikation in der swiyu-App. Dort muss zugestimmt werden, ob die angeforderten Daten mit dem Verifier-Service geteilt werden sollen oder nicht.

Der Verifizierungsablauf im Überblick

Die folgende Abbildung zeigt, wie die Services bei einer Verifikationsanfrage zusammenarbeiten:

Übersicht über den Ablauf einer e-ID Verifikation und deren involvierten Systeme
  1. Um eine e-ID-Verifikation zu initialisieren, ruft unser Shop einen Endpoint im Shop-Backend auf.

  2. Das Backend erstellt eine neue Verifizierungsanfrage mit den vom Kunden anzufordernden Feldern und sendet sie an den Verifier-Service.

  3. Der Verifier-Service initialisiert eine neue Verifikation und antwortet mit der Verifizierungs-ID, dem Verifikations-URL und dem Deeplink.

  4. Das Shop-Backend gibt diese Informationen an den Shop weiter.

  5. Der Shop zeigt einen QR-Code der Verifikations-URL bzw. den Deeplink an, welcher die swiyu-App auf dem Smartphone öffnet.

  6. In der swiyu-App genehmigt die Kundin bzw. der Kunde das Teilen der angeforderten Daten, die dann direkt an den Verifier-Service übertragen werden.

  7. Der Verifier-Service benachrichtigt das Shop-Backend, dass die Verifikation abgeschlossen wurde.

  8. Das Shop-Backend holt das Verifikationsergebnis beim Verifier-Service ab.

  9. Das Shop-Backend sendet das Ergebnis an den Shop, welcher entsprechend darauf reagieren kann.

Die Implementierung im Detail

Onboarding in der swiyu Trust Registry

Um als Verifier-Service agieren zu können, musst du dich bzw. deine Organisation in der swiyu Base & Trust Registry onboarden. Folge dazu dieser Schritt-für-Schritt-Anleitung des Bundes:

https://swiyu-admin-ch.github.io/cookbooks/onboarding-base-and-trust-registry

Am Ende verfügst du über deine persönliche DID (dezentraler Identifikator) sowie die zugehörigen Public/Private Key-Paare. Diese benötigst du für das Setup des Verifier-Service in den folgenden Schritten.

Setup des Verifier-Service

Für das Aufsetzen eines Verifier-Service stellt der Bund eine generische Verifier-Implementierung bereit. Diese findest du hier: https://github.com/swiyu-admin-ch/swiyu-verifier.

Am einfachsten startest du mit der sample.compose.yml-Konfiguration und dem .env-File aus dem Repository.

Definiere in dem .env-File die Umgebungsvariablen passend zu deinem Setup:

  • EXTERNAL_URL: Diese URL zeigt auf den Verifier-Service selbst. Wichtig ist, dass die URL vom mobilen Gerät erreichbar ist, welches die Verifikation durchführt. Während der Entwicklung macht es also Sinn hier die IP des Verifier-Hosts zu verwenden (zB. http://192.168.x.x:8083).

  • VERIFIER_DID: Trage hier die DID ein, welche du während des Onboardings erhalten hast. (zB. id:tdw:AbC[...])

  • VERIFIER_DID_VERIFICATION_METHOD: Hier trägst du die DID inklusive der Verifizierungsmethode ein. Sie sieht genauso aus wie die DID, hat aber einen Zusatz für die Verifikations-Methode (zB. id:tdw:AbC[...]#assert-key-01).

  • VERIFIER_SIGNING_KEY: Hier kommt der Private Key des Assertion Key Pairs hinein, welches während des Onboardings erzeugt wurde. Standardmässig heisst die Datei assert-key-01.

Eine Dokumentation mit allen möglichen Environment-Variablen des Verifier-Services findest du hier: https://swiyu-admin-ch.github.io/cookbooks/onboarding-generic-verifier/#set-the-environment-variables.

Setup Shop-Backend

Wir gehen davon aus, dass bereits ein Shop-Backend existiert. In unserem Fall basiert der Shop auf Vendure. Wie oben beschrieben, muss das Backend einen öffentlichen Endpoint bereitstellen, der beim Verifier-Service eine Verifizierung initialisiert.

Dafür haben wir die GraphQL-Mutation initAgeVerification erstellt, die eine Verifizierung startet, ob der aktuelle Kunde mindestens 16 Jahre alt ist (gesetzliche Anforderung für den Kauf von Bier in der Schweiz). Das könnte natürlich auch ein REST-Endpoint sein oder ein anderes Format, das dein Framework unterstützt.

Dieser Endpoint sendet einen POST-Request an /management/api/verifications des Verifier-Service mit folgendem Body:

 {
    accepted_issuer_dids: ['did:tdw:QmPEZPhDFR4nEYSFK5bMnvECqdpf1tPTPJuWs9QrMjCumw:identifier-reg.trust-infra.swiyu-int.admin.ch:api:v1:did:9a5559f0-b81c-4368-a170-e7b4ae424527'], // <- DID of the e-ID issuer (optional)
    jwt_secured_authorization_request: false, // <- in a production setup this should be set to true
    presentation_definition: {
        id: '<unique-id>', // <- a generated unique id for the request (eg. uuid)
        input_descriptors: [
            {
                id: '<another-unique-id>', // <- another generated unique id for the request (eg. uuid)
                format: {
                    'vc+sd-jwt': {
                        'sd-jwt_alg_values': ['ES256'],
                        'kb-jwt_alg_values': ['ES256'],
                    },
                },
                constraints: {
                    fields: [
                        {
                            path: ['$.vct'],
                            filter: {
                                type: 'string',
                                const: 'betaid-sdjwt',
                            },
                        },
                        {
                           path: ['$.age_over_16'], // <- check if user is at least 16 years old
                        }
                    ],
                },
            },
        ],
    },
}

Wenn die Verifikation erfolgreich initialisiert wurde, erhältst du eine Antwort die neben dem Body zusätzlich folgende Attribute beinhaltet:

  • id: ID der erstellten Verifikation

  • verification_url: URL, an welche die swiyu-App die Verifikation sendet

  • verification_deeplink: Link, welcher verwendet werden kann, um auf Mobilgeräten mit installierter swiyu-App, die App direkt zu öffnen und die Verifikation zu starten

Unser initAgeVerification-Endpoint gibt diese drei Werte als Antwort an den Shop zurück.

Shop-Setup

Die Altersverifikation kann an verschiedenen Stellen des Bestellprozesses stattfinden. Je nach Use Case zum Beispiel beim Start des Checkouts, wenn Produkte mit Altersnachweis im Warenkorb liegen. In unserem Demoshop führen wir die Verifikation bereits vor dem Hinzufügen eines solchen Produkts zum Warenkorb durch, damit der Prozess möglichst früh getestet werden kann.

Zur Initialisierung ruft das Frontend den oben beschriebenen GraphQL-Endpoint initAgeVerification auf. Die Antwort enthält die id, die verification_url und den verification_deeplink.

Wir erzeugen aus dem verification_deeplink einen QR-Code und zeigen diesen an. Auf Mobilgeräten blenden wir zusätzlich einen "In der swiyu-App verifizieren"-Link ein, womit die swiyu-App direkt geöffnet werden kann.

Die swiyu-App sendet das Ergebnis anschliessend über die verification_url, welche im verification_deeplink enthalten ist, an den Verifier-Service.

Setup Callback zum Shop Backend

Sobald der Verifier-Service ein Update für eine aktive Verifikation erhält, fügt er dieser die Antwort der swiyu-App an und setzt deren Status auf SUCCESS oder FAILED.

Danach muss er das Shop-Backend benachrichtigen, damit dieses das Ergebnis prüfen kann.

Dazu stellt der Verifier einen Webhook-Callback bereit, der nach Abschluss einer Verifikation ausgelöst wird. Wir benötigen also einen weiteren Endpoint, welcher diesen POST-Request des Verifiers entgegennimmt.

Webhook-Callback-Endpoint im Shop-Backend

In unserer Demo haben wir einen REST POST-Endpoint /verification-done erstellt. Dieser erhält einen JSON-Body mit folgendem Inhalt:

{
    verification_id: "<ID>",
    timestamp: "<Unix timestamp>"
}

Wie man sieht, erhalten wir keine Detaildaten zur Verifikation selbst. Um diese abzurufen, muss ein GET-Request auf /management/api/verifications/<verification_id> des Verifier-Service ausgeführt werden.

Die Antwort liefert alle Informationen der Verifikation als JSON zurück, darunter den aktuellen state sowie die wallet_response. Diese kommt in folgender Form daher:

 {
    state: 'PENDING' | 'SUCCESS' | 'FAILED';
    
    [...]
    
    wallet_response: {
        error_code: string | null;
        error_description: string | null;
        credential_subject_data: {
            age_over_16?: 'false' | 'true';
    
            [...]
        };
    };
}

Der Endpoint prüft nun, ob die erwartete Verifikation erfolgreich war, und setzt entsprechend den Zustand der aktuellen Kundin bzw. des aktuellen Kunden.

In unserem Fall verwenden wir dafür folgende Bedingung:

!!(
    verificationResult.state === 'SUCCESS' &&
    verificationResult.wallet_response &&
    verificationResult.wallet_response.credential_subject_data.age_over_16 === 'true'
);
  1. Prüfe, ob der Status auf SUCCESS gesetzt ist. Das bedeutet, dass die Person das Teilen der Daten in der swiyu-App genehmigt hat. Bei einer Ablehnung wäre der Status auf FAILED gesetzt.

  2. Prüfe, ob das age_over_16-Property der wallet_response.credential_subject_data auf 'true' gesetzt ist. Achtung: Es handelt sich hierbei nicht um einen boolean-Wert, sondern um einen string (!), der den Wert 'true' oder 'false' aufweist.

Sind beide Bedingungen erfüllt, wissen wir, dass die Person mindestens 16 Jahre alt ist und Bier kaufen darf. Du kannst diese Information zum Beispiel direkt auf dem Kunden (oder wo immer sinnvoll) im Shop-Backend persistieren.

Zuletzt musst du noch diesen Webhook-Endpoint als Callback-URI auf dem Verifier-Service konfigurieren. Dies kannst du mit der folgender Umgebungsvariable tun:

  • WEBHOOK_CALLBACK_URI: Zeigt auf die gerade angelegte /verification-done-URL.

Den Shop über abgeschlossene Verifikation informieren

Zum Schluss muss der Shop über den Abschluss einer Verifikation informiert werden, damit der Einkauf fortgesetzt werden kann.

Das kann auf verschiedene Arten erfolgen. Üblich ist es, bei solchen asynchronen Flows eine permanente Verbindung zum Client über WebSockets oder Server-Sent Events (SSE) aufzubauen. Welche Lösung passt, hängt von deinem Stack ab.

Im Demoshop verwenden wir dazu Server-Sent Events.

Unser Backend stellt dafür einen SSE-Endpoint /updates/:verificationId bereit. Das Frontend ruft diesen nach Erhalt der verificationId vom initAgeVerification-Request auf und initialisiert damit eine Verbindung zum Server, über welche Updates zur definierten Verifikation gesendet werden. Sobald das Backend den /verification-done-Callback erhalten und das Ergebnis geprüft hat, sendet es ein Server-Sent Event an den Client, welcher auf diese verificationId hört.

Geschafft!

Phuu... damit haben wir nun ein vollständig funktionierendes Beispiel einer e-ID-Verifikation. Zugegeben, es hat auch bei mir etwas gedauert, bis alles wie erwartet lief. Aber für ein Produkt, welches sich noch in aktiver Entwicklung befindet, sind der Verifier-Service und der gesamte Verifizierungsflow jedoch bereits sehr gut in den Cookbooks des Bundes dokumentiert: https://swiyu-admin-ch.github.io/cookbooks/.

Nun noch zur Sicherheit

Der Einfachheit halber habe ich Sicherheitsaspekte im oberen Teil bewusst ausgelassen. Folgendes gibt es aber zu beachten:

Verifier /management-Endpoints

Ein kritischer Punkt unseres Setups ist, dass der Verifier-Service theoretisch von Dritten für eigene Verifikationen genutzt werden könnte, solange die /management-APIs öffentlich sind. Wie du diese absicherst, ist hier beschrieben: https://github.com/swiyu-admin-ch/swiyu-verifier?tab=readme-ov-file#security.

Shop-Endpoint /verification-done

Auch der Callback-Endpoint /verification-done im Shop-Backend muss abgesichert werden. Er sollte nur vom Verifier-Service aufrufbar sein. Die Verifier-App bietet dazu folgende Environment-Variablen, um einen API-Key-Header zu setzen, welcher dann bei den jeweiligen Requests vom Verifier-Service mitgesendet wird:

  • WEBHOOK_API_KEY_HEADER: Name des Headers (zB. verifier-api-key)

  • WEBHOOK_API_KEY_VALUE: Der API-Key selbst

Im Shop-Backend kannst du nun den Header auf seine Korrektheit prüfen. Stimmt der API-Key nicht, kannst du die Anfragen mit einem 401 Unauthorized-Status ablehnen.

Weitere Artikel zu diesem Thema