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

Как создать верификатор цифровых учетных данных (руководство для разработчиков)

Узнайте, как создать верификатор цифровых учетных данных с нуля, используя Next.js, OpenID4VP и ISO mDoc. В этом пошаговом руководстве для разработчиков мы покажем, как создать верификатор, который может запрашивать, получать и проверять мобильные водител

Amine

Created: August 20, 2025

Updated: August 21, 2025

Blog-Post-Header-Image

See the original blog version in English here.

DigitalCredentialsDemo Icon

Want to experience digital credentials in action?

Try Digital Credentials

1. Введение#

Подтверждение личности в интернете — это постоянная проблема, которая приводит к зависимости от паролей и обмену конфиденциальными документами по незащищенным каналам. Из-за этого проверка личности для бизнеса стала медленным, дорогим и подверженным мошенничеству процессом. Цифровые учетные данные (Digital Credentials) предлагают новый подход, возвращая пользователям контроль над своими данными. Они являются цифровым эквивалентом физического кошелька, содержащего все — от водительских прав до университетского диплома, но с дополнительными преимуществами: криптографическая защита, сохранение конфиденциальности и возможность мгновенной проверки.

Это руководство представляет собой практическое пошаговое пособие для разработчиков по созданию верификатора цифровых учетных данных. Хотя стандарты уже существуют, практических рекомендаций по их внедрению мало. Это руководство восполняет этот пробел, показывая, как создать верификатор с использованием встроенного в браузер Digital Credential API, протокола представления OpenID4VP и формата учетных данных ISO mDoc (например, мобильные водительские права).

В результате мы получим простое, но функциональное приложение на Next.js, которое сможет запрашивать, получать и проверять цифровые учетные данные из совместимого мобильного кошелька.

Вот краткий обзор готового приложения в действии. Процесс состоит из четырех основных шагов:

Шаг 1: Начальная страница Пользователь попадает на начальную страницу и нажимает «Подтвердить с помощью Digital Identity», чтобы начать процесс.

Шаг 2: Запрос доверия Браузер запрашивает у пользователя подтверждение доверия. Пользователь нажимает «Продолжить», чтобы продолжить.

Шаг 3: Сканирование QR-кода Отображается QR-код, который пользователь сканирует с помощью совместимого приложения-кошелька.

Шаг 4: Расшифрованные учетные данные После успешной проверки приложение отображает расшифрованные данные учетных данных.

1.1 Как это работает#

Магия цифровых учетных данных заключается в простой, но мощной модели «треугольника доверия», в которой участвуют три ключевых игрока:

  • Издатель (Issuer): Доверенный орган (например, государственное учреждение, университет или банк), который криптографически подписывает и выдает учетные данные пользователю.
  • Владелец (Holder): Пользователь, который получает учетные данные и надежно хранит их в личном цифровом кошельке на своем устройстве.
  • Верификатор (Verifier): Приложение или сервис, которому необходимо проверить учетные данные пользователя.

Когда пользователь хочет получить доступ к сервису, он предъявляет учетные данные из своего кошелька. Верификатор может мгновенно проверить их подлинность, не связываясь напрямую с первоначальным издателем.

1.2 Почему верификаторы важны (и почему вы здесь)#

Для процветания этой децентрализованной экосистемы идентификации роль верификатора абсолютно критична. Они являются стражами этой новой инфраструктуры доверия, теми, кто использует учетные данные и делает их полезными в реальном мире. Как показано на диаграмме ниже, верификатор замыкает треугольник доверия, запрашивая, получая и проверяя учетные данные от владельца.

Если вы разработчик, создание сервиса для выполнения этой проверки — это основополагающий навык для следующего поколения безопасных и ориентированных на пользователя приложений. Это руководство создано, чтобы провести вас через этот процесс. Мы рассмотрим все, что вам нужно знать, чтобы создать собственный верификатор проверяемых учетных данных, от основных концепций и стандартов до пошаговых деталей реализации проверки подписей и статуса учетных данных.

Хотите перейти к результату? Вы можете найти полный, готовый проект этого руководства на GitHub. Не стесняйтесь клонировать его и попробовать самостоятельно: https://github.com/corbado/digital-credentials-example

Давайте начнем.

2. Предварительные требования для создания верификатора#

Прежде чем начать, убедитесь, что у вас есть:

  1. Базовое понимание цифровых учетных данных и mdoc
    • Это руководство сосредоточено на формате ISO mDoc (например, для мобильных водительских прав) и не охватывает другие форматы, такие как W3C Verifiable Credentials (VCs). Знакомство с основными концепциями mdoc будет полезным.
  2. Docker и Docker Compose
    • Наш проект использует базу данных MySQL в контейнере Docker для управления состоянием сессии OIDC. Убедитесь, что у вас установлены и запущены оба инструмента.
  3. Выбранный протокол: OpenID4VP
    • Мы будем использовать протокол OpenID4VP (OpenID for Verifiable Presentations) для потока обмена учетными данными.
  4. Готовый технологический стек
    • Используйте TypeScript (Node.js) для логики бэкенда.
    • Используйте Next.js как для бэкенда (API routes), так и для фронтенда (UI).
    • Ключевые библиотеки: библиотеки для декодирования CBOR для парсинга mdoc и клиент MySQL.
  5. Тестовые учетные данные и кошелек
    • Мы будем использовать CMWallet для Android, который понимает запросы OpenID4VP и может представлять учетные данные mdoc.
  6. Базовые знания криптографии
    • Понимание цифровых подписей и концепций открытого/закрытого ключей в контексте mdoc и потоков OIDC.

Теперь мы подробно рассмотрим каждое из этих предварительных требований, начиная со стандартов и протоколов, лежащих в основе этого верификатора на базе mdoc.

2.1 Выбор протоколов#

Наш верификатор создан для следующего:

Стандарт / ПротоколОписание
W3C VCМодель данных W3C Verifiable Credentials. Она определяет стандартную структуру для цифровых учетных данных, включая утверждения, метаданные и доказательства.
SD-JWTВыборочное раскрытие для JWT. Формат для VC на основе JSON Web Tokens, который позволяет владельцам выборочно раскрывать только определенные утверждения из учетных данных, повышая конфиденциальность.
ISO mDocISO/IEC 18013-5. Международный стандарт для мобильных водительских удостоверений (mDL) и других мобильных ID, определяющий структуры данных и протоколы связи для оффлайн- и онлайн-использования.
OpenID4VPOpenID for Verifiable Presentations. Совместимый протокол представления, построенный на OAuth 2.0. Он определяет, как верификатор запрашивает учетные данные, а кошелек владельца их представляет.

В этом руководстве мы используем:

  • OpenID4VP в качестве протокола для запроса и получения учетных данных.
  • ISO mDoc в качестве формата учетных данных (например, для мобильных водительских прав).

Примечание о рамках: Хотя мы кратко представляем W3C VC и SD-JWT для более широкого контекста, это руководство реализует исключительно учетные данные ISO mDoc через OpenID4VP. Учетные данные на основе W3C выходят за рамки этого примера.

2.1.1 ISO mDoc (Mobile Document)#

Стандарт ISO/IEC 18013-5 mDoc определяет структуру и кодирование для мобильных документов, таких как мобильные водительские удостоверения (mDL). Учетные данные mDoc закодированы в CBOR, криптографически подписаны и могут быть представлены в цифровом виде для проверки. Наш верификатор будет сосредоточен на декодировании и проверке этих учетных данных mdoc.

2.1.2 OpenID4VP (OpenID for Verifiable Presentations)#

OpenID4VP — это совместимый протокол для запроса и представления цифровых учетных данных, построенный на базе OAuth 2.0 и OpenID Connect. В этой реализации OpenID4VP используется для:

  • Инициирования потока представления учетных данных (через QR-код или API браузера)
  • Получения учетных данных mdoc из кошелька пользователя
  • Обеспечения безопасного, сессионного и конфиденциального обмена учетными данными

2.2 Выбор технологического стека#

Теперь, когда у нас есть четкое понимание стандартов и протоколов, нам нужно выбрать правильный технологический стек для создания нашего верификатора. Наш выбор направлен на надежность, удобство для разработчиков и совместимость с современной веб-экосистемой.

2.2.1 Язык: TypeScript#

Мы будем использовать TypeScript как для фронтенда, так и для бэкенда. Как надмножество JavaScript, он добавляет статическую типизацию, что помогает выявлять ошибки на ранней стадии, улучшает качество кода и упрощает управление сложными приложениями. В контексте, чувствительном к безопасности, как проверка учетных данных, безопасность типов является огромным преимуществом.

2.2.2 Фреймворк: Next.js#

Next.js — наш выбор фреймворка, потому что он обеспечивает бесшовный, интегрированный опыт для создания full-stack приложений.

  • Для фронтенда: Мы будем использовать Next.js с React для создания пользовательского интерфейса, где инициируется процесс верификации (например, отображение QR-кода).
  • Для бэкенда: Мы будем использовать Next.js API Routes для создания серверных эндпоинтов. Эти эндпоинты отвечают за создание действительных запросов OpenID4VP и за работу в качестве redirect_uri для безопасного получения и проверки окончательного ответа от CMWallet.

2.2.3 Ключевые библиотеки#

Наша реализация опирается на определенный набор библиотек для фронтенда и бэкенда:

  • next: Фреймворк Next.js, используемый как для бэкенд API-маршрутов, так и для фронтенд UI.
  • react и react-dom: Обеспечивают работу пользовательского интерфейса на фронтенде.
  • cbor-web: Используется для декодирования учетных данных mdoc, закодированных в CBOR, в используемые объекты JavaScript.
  • mysql2: Обеспечивает подключение к базе данных MySQL для хранения вызовов-ответов (challenges) и сессий верификации.
  • uuid: Библиотека для генерации уникальных строк вызова-ответа (nonces).
  • @types/uuid: Типы TypeScript для генерации UUID.

Примечание о openid-client: Более продвинутые, готовые к продакшену верификаторы могут использовать библиотеку openid-client для прямой обработки протокола OpenID4VP на бэкенде, что позволяет использовать такие функции, как динамический redirect_uri. В серверном потоке OpenID4VP с redirect_uri, openid-client будет использоваться для прямого парсинга и валидации ответов vp_token. В этом руководстве мы используем более простой, опосредованный браузером поток, который не требует этого, что делает процесс более понятным.

Этот технологический стек обеспечивает надежную, типобезопасную и масштабируемую реализацию верификатора, ориентированную на Digital Credential API браузера и формат учетных данных ISO mDoc.

2.3 Получение тестового кошелька и учетных данных#

Чтобы протестировать ваш верификатор, вам нужен мобильный кошелек, который может взаимодействовать с Digital Credential API браузера.

Мы будем использовать CMWallet, надежный тестовый кошелек, совместимый с OpenID4VP, для Android.

Как установить CMWallet (Android):

  1. Загрузите APK-файл, используя ссылку выше, прямо на ваше устройство Android.
  2. Откройте на вашем устройстве Настройки > Безопасность.
  3. Включите «Установка неизвестных приложений» для браузера, который вы использовали для загрузки файла.
  4. Найдите загруженный APK в папке «Загрузки» и нажмите на него, чтобы начать установку.
  5. Следуйте инструкциям на экране для завершения установки.
  6. Откройте CMWallet, и вы увидите, что он уже загружен с тестовыми учетными данными, готовыми к процессу верификации.

Примечание: Устанавливайте APK-файлы только из источников, которым вы доверяете. Предоставленная ссылка ведет на официальный репозиторий проекта.

2.4 Знания в области криптографии#

Прежде чем мы углубимся в реализацию, важно понять криптографические концепции, лежащие в основе проверяемых учетных данных. Именно это делает их «проверяемыми» и заслуживающими доверия.

2.4.1 Цифровые подписи: основа доверия#

В своей основе проверяемые учетные данные (Verifiable Credential) — это набор утверждений (таких как имя, дата рождения и т.д.), который был подписан цифровой подписью издателя (issuer). Цифровая подпись обеспечивает две критически важные гарантии:

  • Подлинность: Она доказывает, что учетные данные действительно были созданы издателем, а не самозванцем.
  • Целостность: Она доказывает, что учетные данные не были изменены или подделаны с момента их подписания.

2.4.2 Криптография с открытым/закрытым ключом#

Цифровые подписи создаются с использованием криптографии с открытым/закрытым ключом (также называемой асимметричной криптографией). Вот как это работает в нашем контексте:

  1. У издателя есть пара ключей: закрытый ключ, который хранится в секрете, и открытый ключ, который доступен всем (обычно через их DID-документ).
  2. Подписание: Когда издатель создает учетные данные, он использует свой закрытый ключ для генерации уникальной цифровой подписи для этих конкретных данных.
  3. Проверка: Когда наш верификатор получает учетные данные, он использует открытый ключ издателя для проверки подписи. Если проверка проходит успешно, верификатор знает, что учетные данные подлинны и не были подделаны. Любое изменение данных учетных данных сделает подпись недействительной.

Примечание о DID: В этом руководстве мы не разрешаем ключи издателя через DID. В продакшене издатели обычно предоставляют открытые ключи через DID или другие авторитетные эндпоинты, которые верификатор использовал бы для криптографической валидации.

2.4.3 Проверяемые учетные данные как JWT#

Проверяемые учетные данные часто форматируются как JSON Web Tokens (JWT). JWT — это компактный, безопасный для URL способ представления утверждений для передачи между двумя сторонами. Подписанный JWT (также известный как JWS) состоит из трех частей, разделенных точками (.):

  • Заголовок (Header): Содержит метаданные о токене, такие как используемый алгоритм подписи (alg).
  • Полезная нагрузка (Payload): Содержит фактические утверждения проверяемых учетных данных (Verifiable Credential) (vc claim), включая issuer, credentialSubject и т.д.
  • Подпись (Signature): Цифровая подпись, сгенерированная издателем, которая покрывает заголовок и полезную нагрузку.
// Пример структуры JWT [Header].[Payload].[Signature]

Примечание: Проверяемые учетные данные на основе JWT выходят за рамки этой статьи. Эта реализация фокусируется на учетных данных ISO mDoc и OpenID4VP, а не на W3C Verifiable Credentials или учетных данных на основе JWT.

2.4.4 Проверяемое представление: доказательство владения#

Верификатору недостаточно знать, что учетные данные действительны; ему также нужно знать, что человек, представляющий учетные данные, является их законным владельцем. Это предотвращает использование украденных учетных данных.

Эта проблема решается с помощью проверяемого представления (Verifiable Presentation, VP). VP — это обертка вокруг одного или нескольких VC, которая подписана самим владельцем.

Процесс выглядит следующим образом:

  1. Верификатор просит пользователя представить учетные данные.
  2. Кошелек пользователя создает проверяемое представление (Verifiable Presentation), упаковывает в него необходимые учетные данные и подписывает все представление с помощью закрытого ключа владельца.
  3. Кошелек отправляет это подписанное VP верификатору.

Наш верификатор должен выполнить две отдельные проверки подписи:

  1. Проверить учетные данные: Проверить подпись на каждом VC внутри представления, используя открытый ключ издателя. (Доказывает, что учетные данные настоящие).
  2. Проверить представление: Проверить подпись на самом VP, используя открытый ключ владельца. (Доказывает, что человек, представляющий их, является владельцем).

Эта двухуровневая проверка обеспечивает как подлинность учетных данных, так и личность человека, представляющего их, создавая надежную и безопасную модель доверия.

Примечание: Концепция проверяемых представлений, определенная в экосистеме W3C VC, выходит за рамки этой статьи. Термин проверяемое представление (Verifiable Presentation) здесь относится к ответу OpenID4VP vp_token, который ведет себя аналогично W3C VP, но основан на семантике ISO mDoc, а не на модели подписи W3C JSON-LD. Это руководство фокусируется на учетных данных ISO mDoc и OpenID4VP, а не на W3C Verifiable Presentations или их проверке подписи.

3. Обзор архитектуры#

Наша архитектура верификатора использует встроенный в браузер Digital Credential API в качестве безопасного посредника для связи нашего веб-приложения с мобильным CMWallet пользователя. Этот подход упрощает процесс, позволяя браузеру обрабатывать нативное отображение QR-кода и взаимодействие с кошельком.

  • Фронтенд (Next.js & React): Легковесный веб-сайт для пользователя. Его задача — получить объект запроса от нашего бэкенда, передать его в API браузера navigator.credentials.get(), получить результат и переслать его нашему бэкенду для верификации.
  • Бэкенд (Next.js API Routes): Движущая сила верификатора. Он генерирует действительный объект запроса для API браузера и предоставляет эндпоинт для получения представления учетных данных от фронтенда для окончательной проверки.
  • Браузер (Credential API): Посредник. Он получает объект запроса от нашего фронтенда, понимает протокол openid4vp и нативно генерирует QR-код. Затем он ожидает ответа от кошелька.
  • CMWallet (Мобильное приложение): Кошелек пользователя. Он сканирует QR-код, обрабатывает запрос, получает согласие пользователя и отправляет подписанный ответ обратно в браузер.

Вот диаграмма последовательности, иллюстрирующая полный и точный процесс:

Объяснение процесса:

  1. Инициация: Пользователь нажимает кнопку «Verify» на нашем фронтенде.
  2. Объект запроса: Фронтенд вызывает наш бэкенд (/api/verify/start), который генерирует объект запроса, содержащий запрос и nonce, а затем возвращает его.
  3. Вызов API браузера: Фронтенд вызывает navigator.credentials.get() с объектом запроса.
  4. Нативный QR-код: Браузер видит запрос протокола openid4vp и нативно отображает QR-код. Промис .get() теперь находится в состоянии ожидания.

Примечание: Этот процесс с QR-кодом происходит в десктопных браузерах. В мобильных браузерах (Android Chrome с включенным экспериментальным флагом) браузер может напрямую взаимодействовать с совместимыми кошельками на том же устройстве, устраняя необходимость сканирования QR-кода. Чтобы включить эту функцию в Android Chrome, перейдите по адресу chrome://flags#web-identity-digital-credentials и установите флаг в положение «Enabled».

  1. Сканирование и представление: Пользователь сканирует QR-код с помощью CMWallet. Кошелек получает одобрение пользователя и отправляет проверяемое представление (Verifiable Presentation) обратно в браузер.
  2. Разрешение промиса: Браузер получает ответ, и исходный промис .get() на фронтенде наконец разрешается, доставляя полезную нагрузку представления.
  3. Верификация на бэкенде: Фронтенд отправляет POST-запрос с полезной нагрузкой представления на эндпоинт нашего бэкенда /api/verify/finish. Бэкенд проверяет nonce и учетные данные.
  4. Результат: Бэкенд возвращает окончательное сообщение об успехе или неудаче на фронтенд, который обновляет UI.

4. Создание верификатора#

Теперь, когда у нас есть твердое понимание стандартов, протоколов и архитектурного потока, мы можем приступить к созданию нашего верификатора.

Следите за нами или используйте готовый код

Сейчас мы пошагово пройдем через настройку и реализацию кода. Если вы предпочитаете сразу перейти к готовому продукту, вы можете клонировать полный проект из нашего репозитория на GitHub и запустить его локально.

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

4.1 Настройка проекта#

Сначала мы инициализируем новый проект Next.js, установим необходимые зависимости и запустим нашу базу данных.

4.1.1 Инициализация приложения Next.js#

Откройте терминал, перейдите в каталог, где вы хотите создать свой проект, и выполните следующую команду. Мы используем App Router, TypeScript и Tailwind CSS для этого проекта.

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

Эта команда создает каркас нового приложения Next.js в вашем текущем каталоге.

4.1.2 Установка зависимостей#

Далее нам нужно установить библиотеки, которые будут обрабатывать декодирование CBOR, подключения к базе данных и генерацию UUID.

npm install cbor-web mysql2 uuid @types/uuid

Эта команда устанавливает:

  • cbor-web: Для декодирования полезной нагрузки учетных данных mdoc.
  • mysql2: Клиент MySQL для нашей базы данных.
  • uuid: Для генерации уникальных строк вызова-ответа.
  • @types/uuid: Типы TypeScript для библиотеки uuid.

4.1.3 Запуск базы данных#

Нашему бэкенду требуется база данных MySQL для хранения данных сессии OIDC, что обеспечивает безопасность и сохранение состояния каждого потока верификации. Мы включили файл docker-compose.yml, чтобы упростить этот процесс.

Если вы клонировали репозиторий, вы можете просто выполнить docker-compose up -d. Если вы создаете проект с нуля, создайте файл с именем docker-compose.yml со следующим содержимым:

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:

Эта настройка Docker Compose также требует скрипта инициализации SQL. Создайте каталог с именем sql и внутри него файл с именем init.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) );

Когда оба файла будут на месте, откройте терминал в корне проекта и выполните:

docker-compose up -d

Эта команда запустит контейнер MySQL в фоновом режиме.

4.2 Обзор архитектуры приложения Next.js#

Наше приложение Next.js структурировано таким образом, чтобы разделить обязанности между фронтендом и бэкендом, хотя они и являются частью одного проекта.

  • Фронтенд (src/app/page.tsx): Одна страница React, которая инициирует процесс верификации и отображает результат. Она взаимодействует с Digital Credential API браузера.
  • Бэкенд API Routes (src/app/api/verify/...):
    • start/route.ts: Генерирует запрос OpenID4VP и защитный nonce.
    • finish/route.ts: Получает представление от кошелька (через браузер), проверяет nonce и декодирует учетные данные.
  • Библиотека (src/lib/):
    • database.ts: Управляет всеми взаимодействиями с базой данных (создание вызовов-ответов, проверка сессий).
    • crypto.ts: Обрабатывает декодирование учетных данных mDoc на основе CBOR.

Вот диаграмма, иллюстрирующая внутреннюю архитектуру:

4.3 Создание фронтенда#

Наш фронтенд намеренно сделан легковесным. Его основная задача — служить пользовательским триггером для процесса верификации и взаимодействовать как с нашим бэкендом, так и с нативными возможностями браузера по обработке учетных данных. Он не содержит сложной логики протоколов; все это делегировано.

В частности, фронтенд будет обрабатывать следующее:

  • Взаимодействие с пользователем: Предоставляет простой интерфейс, например кнопку «Verify», для запуска процесса.
  • Управление состоянием: Управляет состоянием UI, показывая индикаторы загрузки во время верификации и отображая окончательное сообщение об успехе или ошибке.
  • Взаимодействие с бэкендом (запрос): Вызывает /api/verify/start и получает структурированную JSON-нагрузку (protocol, request, state), описывающую, что именно должен представить кошелек.
  • Вызов API браузера: Передает этот JSON-объект в navigator.credentials.get(), который отображает нативный QR-код и ожидает ответа кошелька.
  • Взаимодействие с бэкендом (ответ): Как только API браузера возвращает Verifiable Presentation, он отправляет эти данные на наш эндпоинт /api/verify/finish в POST-запросе для окончательной проверки на стороне сервера.
  • Отображение результатов: Обновляет UI, чтобы сообщить пользователю, была ли верификация успешной или неудачной, на основе ответа от бэкенда.

Основная логика находится в функции startVerification:

// src/app/page.tsx const startVerification = async () => { setLoading(true); setVerificationResult(null); try { // 1. Проверяем, поддерживает ли браузер API if (!navigator.credentials?.get) { throw new Error("Browser does not support the Credential API."); } // 2. Запрашиваем у нашего бэкенда объект запроса const res = await fetch("/api/verify/start"); const { protocol, request } = await res.json(); // 3. Передаем этот объект браузеру – это запускает нативный QR-код const credential = await (navigator.credentials as any).get({ mediation: "required", digital: { requests: [ { protocol, // "openid4vp" data: request, // содержит dcql_query, nonce и т.д. }, ], }, }); // 4. Пересылаем ответ кошелька (от браузера) на наш эндпоинт finish для серверных проверок const verifyRes = await fetch("/api/verify/finish", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(credential), }); const result = await verifyRes.json(); if (verifyRes.ok && result.verified) { setVerificationResult(`Success: ${result.message}`); } else { throw new Error(result.message || "Verification failed."); } } catch (err) { setVerificationResult(`Error: ${(err as Error).message}`); } finally { setLoading(false); } };

Эта функция показывает четыре ключевых шага логики фронтенда: проверка поддержки API, получение запроса от бэкенда, вызов API браузера и отправка результата обратно для верификации. Остальная часть файла — это стандартный шаблон React для управления состоянием и рендеринга UI, который вы можете посмотреть в репозитории на GitHub.

Почему digital и mediation: 'required'?#

Вы могли заметить, что наш вызов navigator.credentials.get() выглядит иначе, чем в более простых примерах. Это потому, что мы строго придерживаемся официальной спецификации W3C Digital Credentials API.

  • digital Member: Спецификация требует, чтобы все запросы цифровых учетных данных были вложены в объект digital. Это обеспечивает четкое, стандартизированное пространство имен для этого API, отличая его от других типов учетных данных (таких как password или federated) и позволяя будущие расширения без конфликтов.

  • mediation: 'required': Эта опция является ключевой функцией безопасности и пользовательского опыта. Она требует, чтобы пользователь активно взаимодействовал с запросом (например, сканирование биометрии, ввод PIN-кода или экран согласия) для одобрения запроса учетных данных. Без этого веб-сайт потенциально мог бы пытаться незаметно получить доступ к учетным данным в фоновом режиме, что представляет значительный риск для конфиденциальности. Требуя посредничества, мы гарантируем, что пользователь всегда контролирует ситуацию и дает явное согласие на каждую транзакцию.

4.4 Создание бэкенд-эндпоинтов#

С готовым UI на React нам теперь нужны два API-маршрута, которые выполняют основную работу на сервере:

  1. /api/verify/start – создает запрос OpenID4VP, сохраняет одноразовый challenge в MySQL и передает все обратно в браузер.
  2. /api/verify/finish – получает ответ кошелька, проверяет challenge, верифицирует и декодирует учетные данные и, наконец, возвращает краткий JSON-результат в UI.

4.4.1 /api/verify/start: Генерация запроса OpenID4VP#

// src/app/api/verify/start/route.ts import { NextResponse } from "next/server"; import { v4 as uuidv4 } from "uuid"; import { createChallenge, cleanupExpiredChallenges } from "@/lib/database"; export async function GET() { // 1️⃣ Создаем короткоживущий, случайный nonce (challenge) const challenge = uuidv4(); const challengeId = uuidv4(); const expiresAt = new Date(Date.now() + 5 * 60 * 1000); await createChallenge(challengeId, challenge, expiresAt); cleanupExpiredChallenges().catch(console.error); // 2️⃣ Создаем запрос DCQL, описывающий, *что* мы хотим const dcqlQuery = { credentials: [ { id: "cred1", format: "mso_mdoc", meta: { doctype_value: "eu.europa.ec.eudi.pid.1" }, claims: [ { path: ["eu.europa.ec.eudi.pid.1", "family_name"] }, { path: ["eu.europa.ec.eudi.pid.1", "given_name"] }, { path: ["eu.europa.ec.eudi.pid.1", "birth_date"] }, ], }, ], }; // 3️⃣ Возвращаем объект, который браузер может передать в navigator.credentials.get() return NextResponse.json({ protocol: "openid4vp", // говорит браузеру, какой протокол кошелька использовать request: { dcql_query: dcqlQuery, // ЧТО представить nonce: challenge, // защита от повторного воспроизведения response_type: "vp_token", response_mode: "dc_api", // кошелек отправит POST-запрос напрямую на /finish }, state: { credential_type: "mso_mdoc", // сохраняется для последующих проверок nonce: challenge, challenge_id: challengeId, }, }); }

Ключевые параметры

nonceкриптографический вызов-ответ (cryptographic challenge), который связывает запрос и ответ (предотвращает повторное воспроизведение). • dcql_query – Объект, описывающий точные утверждения, которые нам нужны. В этом руководстве мы используем структуру dcql_query, вдохновленную недавними черновиками Digital Credential Query Language, хотя это еще не окончательный стандарт. • state – произвольный JSON, возвращаемый кошельком, чтобы мы могли найти запись в БД.

4.4.2 Вспомогательные функции для базы данных#

Файл src/lib/database.ts оборачивает базовые операции MySQL для challenges и сессий верификации (вставка, чтение, отметка об использовании). Хранение этой логики в одном модуле позволяет легко заменить хранилище данных позже.


4.5 /api/verify/finish: Валидация и декодирование представления#

// src/app/api/verify/finish/route.ts import { NextResponse, NextRequest } from "next/server"; import { v4 as uuidv4 } from "uuid"; import { getChallenge, markChallengeAsUsed, createVerificationSession, updateVerificationSession, } from "@/lib/database"; import { decodeDigitalCredential, decodeAllNamespaces } from "@/lib/crypto"; export async function POST(request: NextRequest) { const body = await request.json(); // 1️⃣ Извлекаем части проверяемого представления const vpTokenMap = body.vp_token ?? body.data?.vp_token; const state = body.state; const mdocToken = vpTokenMap?.cred1; // мы запрашивали этот ID в dcqlQuery if (!vpTokenMap || !state || !mdocToken) { return NextResponse.json( { verified: false, message: "Malformed response" }, { status: 400 }, ); } // 2️⃣ Валидация одноразового challenge const stored = await getChallenge(state.nonce); if (!stored) { return NextResponse.json( { verified: false, message: "Invalid or expired challenge" }, { status: 400 }, ); } const sessionId = uuidv4(); await createVerificationSession(sessionId, stored.id); // 3️⃣ (Псевдо) криптографические проверки – замените на реальную валидацию mDL в продакшене // В реальном приложении вы бы использовали специальную библиотеку для выполнения полной // криптографической валидации подписи mdoc с открытым ключом издателя. const isValid = mdocToken.length > 0; if (!isValid) { await updateVerificationSession(sessionId, "failed", { reason: "mdoc validation failed", }); return NextResponse.json( { verified: false, message: "Credential validation failed" }, { status: 400 }, ); } // 4️⃣ Декодируем мобильное водительское удостоверение (mdoc) в читаемый JSON const decoded = await decodeDigitalCredential(mdocToken); const readable = decodeAllNamespaces(decoded)["eu.europa.ec.eudi.pid.1"]; await markChallengeAsUsed(state.nonce); await updateVerificationSession(sessionId, "verified", { readable }); return NextResponse.json({ verified: true, message: "mdoc credential verified successfully!", credentialData: readable, sessionId, }); }

Важные поля в ответе кошелька

vp_token – карта, которая содержит каждые учетные данные, возвращаемые кошельком. В нашем демо мы извлекаем vp_token.cred1. • state – эхо-ответ на blob, который мы предоставили в /start; содержит nonce, чтобы мы могли найти запись в БД. • mdocToken – структура CBOR, закодированная в Base64URL, которая представляет ISO mDoc.

4.6 Декодирование учетных данных mdoc#

Когда верификатор получает учетные данные mdoc от браузера, это строка Base64URL, содержащая бинарные данные, закодированные в CBOR. Чтобы извлечь фактические утверждения, эндпоинт finish выполняет многоэтапный процесс декодирования с использованием вспомогательных функций из src/lib/crypto.ts.

4.6.1 Шаг 1: Декодирование Base64URL и CBOR#

Функция decodeDigitalCredential обрабатывает преобразование из закодированной строки в используемый объект:

// src/lib/crypto.ts export async function decodeDigitalCredential(encodedCredential: string) { // 1. Преобразуем Base64URL в стандартный Base64 const base64UrlToBase64 = (input: string) => { let base64 = input.replace(/-/g, "+").replace(/_/g, "/"); const pad = base64.length % 4; if (pad) base64 += "=".repeat(4 - pad); return base64; }; const base64 = base64UrlToBase64(encodedCredential); // 2. Декодируем Base64 в бинарные данные const binaryString = atob(base64); const byteArray = Uint8Array.from(binaryString, (char) => char.charCodeAt(0)); // 3. Декодируем CBOR const decoded = await cbor.decodeFirst(byteArray); return decoded; }
  • Base64URL в Base64: Преобразует учетные данные из Base64URL в стандартную кодировку Base64.
  • Base64 в бинарные данные: Декодирует строку Base64 в массив бинарных байтов.
  • Декодирование CBOR: Использует библиотеку cbor-web для декодирования бинарных данных в структурированный объект JavaScript.

4.6.2 Шаг 2: Извлечение утверждений из пространств имен#

Функция decodeAllNamespaces дополнительно обрабатывает декодированный объект CBOR для извлечения фактических утверждений из соответствующих пространств имен:

// src/lib/crypto.ts export function decodeAllNamespaces(jsonObj) { const decoded = {}; try { jsonObj.documents.forEach((doc, idx) => { // 1) issuerSigned.nameSpaces: const issuerNS = doc.issuerSigned?.nameSpaces || {}; Object.entries(issuerNS).forEach(([nsName, entries]) => { if (!decoded[nsName]) decoded[nsName] = {}; (entries as any[]).forEach((entry) => { const bytes = Uint8Array.from(entry.value); const decodedEntry = cbor.decodeFirstSync(bytes); Object.assign(decoded[nsName], decodedEntry); }); }); // 2) deviceSigned.nameSpaces (если есть): const deviceNS = doc.deviceSigned?.nameSpaces; if (deviceNS?.value?.data) { const bytes = Uint8Array.from(deviceNS.value); decoded[`deviceSigned_ns_${idx}`] = cbor.decodeFirstSync(bytes); } }); } catch (e) { console.error(e); } return decoded; }
  • Итерирует по всем документам в декодированных учетных данных.
  • Декодирует каждое пространство имен (например, eu.europa.ec.eudi.pid.1) для извлечения фактических значений утверждений (таких как имя, дата рождения и т.д.).
  • Обрабатывает как подписанные издателем, так и подписанные устройством пространства имен, если они присутствуют.

Пример вывода#

После выполнения этих шагов эндпоинт finish получает человекочитаемый объект, содержащий утверждения из mdoc, например:

{ "family_name": "Doe", "given_name": "John", "birth_date": "1990-01-01" }

Этот процесс гарантирует, что верификатор может безопасно и надежно извлекать необходимую информацию из учетных данных mdoc для отображения и дальнейшей обработки.

4.7 Отображение результата в UI#

Эндпоинт finish возвращает минимальный JSON-объект на фронтенд:

{ "verified": true, "message": "mdoc credential verified successfully!", "credentialData": { "family_name": "Doe", "given_name": "John", "birth_date": "1990-01-01" } }

Фронтенд получает этот ответ в startVerification() и просто сохраняет его в состоянии React, чтобы мы могли отобразить красивую карточку подтверждения или показать отдельные утверждения – например, «Добро пожаловать, John Doe (родился 1990-01-01)!».

5. Запуск верификатора и следующие шаги#

Теперь у вас есть полный, работающий верификатор, который использует нативные возможности браузера для обработки учетных данных. Вот как запустить его локально и что можно сделать, чтобы превратить его из доказательства концепции в готовое к продакшену приложение.

5.1 Как запустить пример#

  1. Клонируйте репозиторий:

    git clone https://github.com/corbado/digital-credentials-example.git cd digital-credentials-example
  2. Установите зависимости:

    npm install
  3. Запустите базу данных: Убедитесь, что Docker запущен на вашем компьютере, затем запустите контейнер MySQL:

    docker-compose up -d
  4. Запустите приложение:

    npm run dev

    Откройте браузер по адресу http://localhost:3000, и вы должны увидеть UI верификатора. Теперь вы можете использовать свой CMWallet, чтобы отсканировать QR-код и завершить процесс верификации.

5.2 Следующие шаги: от демо к продакшену#

Это руководство предоставляет foundational building blocks для верификатора. Чтобы сделать его готовым к продакшену, вам потребуется реализовать несколько дополнительных функций:

  • Полная криптографическая валидация: Текущая реализация использует проверку-заглушку (mdocToken.length > 0). В реальном сценарии вы должны выполнять полную криптографическую валидацию подписи mdoc с использованием открытого ключа издателя (например, путем разрешения их DID или получения их сертификата открытого ключа). Для стандартов разрешения DID обратитесь к спецификации W3C DID Resolution.

  • Проверка отзыва издателем: Учетные данные могут быть отозваны издателем до истечения срока их действия. Продакшен-верификатор должен проверять статус учетных данных, запрашивая список отзыва или эндпоинт статуса, предоставленный издателем. W3C Verifiable Credentials Status List предоставляет стандарт для списков отзыва учетных данных.

  • Надежная обработка ошибок и безопасность: Добавьте комплексную обработку ошибок, валидацию ввода, ограничение скорости запросов на API-эндпоинтах и убедитесь, что все коммуникации происходят по HTTPS (TLS) для защиты данных при передаче. OWASP API Security Guidelines предоставляют комплексные рекомендации по безопасности API.

  • Поддержка нескольких типов учетных данных: Расширьте логику для обработки различных значений doctype и форматов учетных данных, если вы ожидаете получать не только европейские учетные данные Digital Identity (EUDI) PID. W3C Verifiable Credentials Data Model предоставляет исчерпывающие спецификации форматов VC.

5.3 Что выходит за рамки этого руководства#

Этот пример намеренно сфокусирован на основном потоке, опосредованном браузером, чтобы сделать его легким для понимания. Следующие темы считаются выходящими за рамки:

  • Безопасность на уровне продакшена: Верификатор предназначен для образовательных целей и не имеет усиленной защиты, необходимой для реальной среды.
  • W3C Verifiable Credentials: Это руководство фокусируется исключительно на формате ISO mDoc для мобильных водительских прав. Оно не охватывает другие популярные форматы, такие как JWT-VC или VC с Linked Data Proofs (LD-Proofs).
  • Продвинутые потоки OpenID4VP: Мы не реализуем более сложные функции OpenID4VP, такие как прямая связь кошелька с бэкендом с использованием redirect_uri или динамическая регистрация клиента.

Опираясь на эту основу и внедряя эти следующие шаги, вы можете разработать надежный и безопасный верификатор, способный доверять и проверять цифровые учетные данные в ваших собственных приложениях.

Заключение#

Вот и все! Менее чем в 250 строках TypeScript мы создали сквозной верификатор, который:

  1. Публикует запрос для API учетных данных браузера.
  2. Позволяет любому совместимому кошельку предоставить проверяемое представление.
  3. Проверяет представление на сервере.
  4. Обновляет UI в реальном времени.

В продакшене вы бы заменили проверку-заглушку на полную проверку ISO 18013-5, добавили бы проверку отзыва издателем, ограничение скорости запросов, аудит-логирование и, конечно, сквозное шифрование TLS — но основные строительные блоки остаются точно такими же.

Ресурсы#

Вот некоторые из ключевых ресурсов, спецификаций и инструментов, используемых или упомянутых в этом руководстве:

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

Start Free Trial

Share this article


LinkedInTwitterFacebook

Table of Contents