Узнайте, как создать верификатор цифровых учетных данных с нуля, используя Next.js, OpenID4VP и ISO mDoc. В этом пошаговом руководстве для разработчиков мы покажем, как создать верификатор, который может запрашивать, получать и проверять мобильные водител
Amine
Created: August 20, 2025
Updated: August 21, 2025
See the original blog version in English here.
Подтверждение личности в интернете — это постоянная проблема, которая приводит к зависимости от паролей и обмену конфиденциальными документами по незащищенным каналам. Из-за этого проверка личности для бизнеса стала медленным, дорогим и подверженным мошенничеству процессом. Цифровые учетные данные (Digital Credentials) предлагают новый подход, возвращая пользователям контроль над своими данными. Они являются цифровым эквивалентом физического кошелька, содержащего все — от водительских прав до университетского диплома, но с дополнительными преимуществами: криптографическая защита, сохранение конфиденциальности и возможность мгновенной проверки.
Это руководство представляет собой практическое пошаговое пособие для разработчиков по созданию верификатора цифровых учетных данных. Хотя стандарты уже существуют, практических рекомендаций по их внедрению мало. Это руководство восполняет этот пробел, показывая, как создать верификатор с использованием встроенного в браузер Digital Credential API, протокола представления OpenID4VP и формата учетных данных ISO mDoc (например, мобильные водительские права).
В результате мы получим простое, но функциональное приложение на Next.js, которое сможет запрашивать, получать и проверять цифровые учетные данные из совместимого мобильного кошелька.
Вот краткий обзор готового приложения в действии. Процесс состоит из четырех основных шагов:
Шаг 1: Начальная страница Пользователь попадает на начальную страницу и нажимает «Подтвердить с помощью Digital Identity», чтобы начать процесс.
Шаг 2: Запрос доверия Браузер запрашивает у пользователя подтверждение доверия. Пользователь нажимает «Продолжить», чтобы продолжить.
Шаг 3: Сканирование QR-кода Отображается QR-код, который пользователь сканирует с помощью совместимого приложения-кошелька.
Шаг 4: Расшифрованные учетные данные После успешной проверки приложение отображает расшифрованные данные учетных данных.
Recent Articles
📝
Как создать верификатор цифровых учетных данных (руководство для разработчиков)
📝
Как создать эмитент цифровых удостоверений (руководство для разработчиков)
📖
Резидентный ключ WebAuthn: обнаруживаемые учетные данные как Passkeys
🔑
Доступ по физическим пропускам и Passkeys: техническое руководство
🔑
Внедрение обязательной MFA и переход на Passkeys: лучшие практики
Магия цифровых учетных данных заключается в простой, но мощной модели «треугольника доверия», в которой участвуют три ключевых игрока:
Когда пользователь хочет получить доступ к сервису, он предъявляет учетные данные из своего кошелька. Верификатор может мгновенно проверить их подлинность, не связываясь напрямую с первоначальным издателем.
Для процветания этой децентрализованной экосистемы идентификации роль верификатора абсолютно критична. Они являются стражами этой новой инфраструктуры доверия, теми, кто использует учетные данные и делает их полезными в реальном мире. Как показано на диаграмме ниже, верификатор замыкает треугольник доверия, запрашивая, получая и проверяя учетные данные от владельца.
Если вы разработчик, создание сервиса для выполнения этой проверки — это основополагающий навык для следующего поколения безопасных и ориентированных на пользователя приложений. Это руководство создано, чтобы провести вас через этот процесс. Мы рассмотрим все, что вам нужно знать, чтобы создать собственный верификатор проверяемых учетных данных, от основных концепций и стандартов до пошаговых деталей реализации проверки подписей и статуса учетных данных.
Хотите перейти к результату? Вы можете найти полный, готовый проект этого руководства на GitHub. Не стесняйтесь клонировать его и попробовать самостоятельно: https://github.com/corbado/digital-credentials-example
Давайте начнем.
Прежде чем начать, убедитесь, что у вас есть:
Теперь мы подробно рассмотрим каждое из этих предварительных требований, начиная со стандартов и протоколов, лежащих в основе этого верификатора на базе mdoc.
Наш верификатор создан для следующего:
Стандарт / Протокол | Описание |
---|---|
W3C VC | Модель данных W3C Verifiable Credentials. Она определяет стандартную структуру для цифровых учетных данных, включая утверждения, метаданные и доказательства. |
SD-JWT | Выборочное раскрытие для JWT. Формат для VC на основе JSON Web Tokens, который позволяет владельцам выборочно раскрывать только определенные утверждения из учетных данных, повышая конфиденциальность. |
ISO mDoc | ISO/IEC 18013-5. Международный стандарт для мобильных водительских удостоверений (mDL) и других мобильных ID, определяющий структуры данных и протоколы связи для оффлайн- и онлайн-использования. |
OpenID4VP | OpenID for Verifiable Presentations. Совместимый протокол представления, построенный на OAuth 2.0. Он определяет, как верификатор запрашивает учетные данные, а кошелек владельца их представляет. |
В этом руководстве мы используем:
Примечание о рамках: Хотя мы кратко представляем W3C VC и SD-JWT для более широкого контекста, это руководство реализует исключительно учетные данные ISO mDoc через OpenID4VP. Учетные данные на основе W3C выходят за рамки этого примера.
Стандарт ISO/IEC 18013-5 mDoc определяет структуру и кодирование для мобильных документов, таких как мобильные водительские удостоверения (mDL). Учетные данные mDoc закодированы в CBOR, криптографически подписаны и могут быть представлены в цифровом виде для проверки. Наш верификатор будет сосредоточен на декодировании и проверке этих учетных данных mdoc.
OpenID4VP — это совместимый протокол для запроса и представления цифровых учетных данных, построенный на базе OAuth 2.0 и OpenID Connect. В этой реализации OpenID4VP используется для:
Теперь, когда у нас есть четкое понимание стандартов и протоколов, нам нужно выбрать правильный технологический стек для создания нашего верификатора. Наш выбор направлен на надежность, удобство для разработчиков и совместимость с современной веб-экосистемой.
Мы будем использовать TypeScript как для фронтенда, так и для бэкенда. Как надмножество JavaScript, он добавляет статическую типизацию, что помогает выявлять ошибки на ранней стадии, улучшает качество кода и упрощает управление сложными приложениями. В контексте, чувствительном к безопасности, как проверка учетных данных, безопасность типов является огромным преимуществом.
Next.js — наш выбор фреймворка, потому что он обеспечивает бесшовный, интегрированный опыт для создания full-stack приложений.
redirect_uri
для безопасного получения и проверки окончательного
ответа от CMWallet.Наша реализация опирается на определенный набор библиотек для фронтенда и бэкенда:
Примечание о openid-client
: Более продвинутые, готовые к продакшену верификаторы
могут использовать библиотеку openid-client
для прямой обработки протокола OpenID4VP
на бэкенде, что позволяет использовать такие функции, как динамический redirect_uri
. В
серверном потоке OpenID4VP с redirect_uri
, openid-client
будет использоваться для
прямого парсинга и валидации ответов vp_token
. В этом руководстве мы используем более
простой, опосредованный браузером поток, который не требует этого, что делает процесс
более понятным.
Этот технологический стек обеспечивает надежную, типобезопасную и масштабируемую реализацию верификатора, ориентированную на Digital Credential API браузера и формат учетных данных ISO mDoc.
Чтобы протестировать ваш верификатор, вам нужен мобильный кошелек, который может взаимодействовать с Digital Credential API браузера.
Мы будем использовать CMWallet, надежный тестовый кошелек, совместимый с OpenID4VP, для Android.
Как установить CMWallet (Android):
Примечание: Устанавливайте APK-файлы только из источников, которым вы доверяете. Предоставленная ссылка ведет на официальный репозиторий проекта.
Прежде чем мы углубимся в реализацию, важно понять криптографические концепции, лежащие в основе проверяемых учетных данных. Именно это делает их «проверяемыми» и заслуживающими доверия.
В своей основе проверяемые учетные данные (Verifiable Credential) — это набор утверждений (таких как имя, дата рождения и т.д.), который был подписан цифровой подписью издателя (issuer). Цифровая подпись обеспечивает две критически важные гарантии:
Цифровые подписи создаются с использованием криптографии с открытым/закрытым ключом (также называемой асимметричной криптографией). Вот как это работает в нашем контексте:
Примечание о DID: В этом руководстве мы не разрешаем ключи издателя через DID. В продакшене издатели обычно предоставляют открытые ключи через DID или другие авторитетные эндпоинты, которые верификатор использовал бы для криптографической валидации.
Проверяемые учетные данные часто форматируются как JSON Web Tokens (JWT). JWT — это
компактный, безопасный для URL способ представления утверждений для передачи между двумя
сторонами. Подписанный JWT (также известный как JWS) состоит из трех частей, разделенных
точками (.
):
alg
).vc
claim), включая issuer
, credentialSubject
и т.д.// Пример структуры JWT [Header].[Payload].[Signature]
Примечание: Проверяемые учетные данные на основе JWT выходят за рамки этой статьи. Эта реализация фокусируется на учетных данных ISO mDoc и OpenID4VP, а не на W3C Verifiable Credentials или учетных данных на основе JWT.
Верификатору недостаточно знать, что учетные данные действительны; ему также нужно знать, что человек, представляющий учетные данные, является их законным владельцем. Это предотвращает использование украденных учетных данных.
Эта проблема решается с помощью проверяемого представления (Verifiable Presentation, VP). VP — это обертка вокруг одного или нескольких VC, которая подписана самим владельцем.
Процесс выглядит следующим образом:
Наш верификатор должен выполнить две отдельные проверки подписи:
Эта двухуровневая проверка обеспечивает как подлинность учетных данных, так и личность человека, представляющего их, создавая надежную и безопасную модель доверия.
Примечание: Концепция проверяемых представлений, определенная в экосистеме W3C VC,
выходит за рамки этой статьи. Термин
проверяемое представление (Verifiable Presentation)
здесь относится к ответу OpenID4VP vp_token
, который ведет себя аналогично W3C VP, но
основан на семантике ISO mDoc, а не на модели подписи W3C JSON-LD.
Это руководство фокусируется на учетных данных ISO mDoc и OpenID4VP, а не на W3C
Verifiable Presentations или их проверке подписи.
Наша архитектура верификатора использует встроенный в браузер Digital Credential API в качестве безопасного посредника для связи нашего веб-приложения с мобильным CMWallet пользователя. Этот подход упрощает процесс, позволяя браузеру обрабатывать нативное отображение QR-кода и взаимодействие с кошельком.
navigator.credentials.get()
, получить результат и переслать его нашему бэкенду для
верификации.openid4vp
и нативно генерирует QR-код. Затем он ожидает ответа от
кошелька.Вот диаграмма последовательности, иллюстрирующая полный и точный процесс:
Объяснение процесса:
/api/verify/start
), который
генерирует объект запроса, содержащий запрос и nonce, а затем возвращает его.navigator.credentials.get()
с объектом
запроса.openid4vp
и нативно
отображает QR-код. Промис .get()
теперь находится в состоянии ожидания.Примечание: Этот процесс с QR-кодом происходит в десктопных браузерах. В мобильных
браузерах (Android Chrome с включенным экспериментальным флагом) браузер может напрямую
взаимодействовать с совместимыми кошельками на том же
устройстве, устраняя необходимость сканирования QR-кода. Чтобы включить эту функцию в
Android Chrome, перейдите по адресу chrome://flags#web-identity-digital-credentials
и
установите флаг в положение «Enabled».
.get()
на фронтенде
наконец разрешается, доставляя полезную нагрузку представления./api/verify/finish
. Бэкенд проверяет nonce и
учетные данные.Теперь, когда у нас есть твердое понимание стандартов, протоколов и архитектурного потока, мы можем приступить к созданию нашего верификатора.
Следите за нами или используйте готовый код
Сейчас мы пошагово пройдем через настройку и реализацию кода. Если вы предпочитаете сразу перейти к готовому продукту, вы можете клонировать полный проект из нашего репозитория на GitHub и запустить его локально.
git clone https://github.com/corbado/digital-credentials-example.git
Сначала мы инициализируем новый проект Next.js, установим необходимые зависимости и запустим нашу базу данных.
Откройте терминал, перейдите в каталог, где вы хотите создать свой проект, и выполните следующую команду. Мы используем App Router, TypeScript и Tailwind CSS для этого проекта.
npx create-next-app@latest . --ts --eslint --tailwind --app --src-dir --import-alias "@/*" --use-npm
Эта команда создает каркас нового приложения Next.js в вашем текущем каталоге.
Далее нам нужно установить библиотеки, которые будут обрабатывать декодирование CBOR, подключения к базе данных и генерацию UUID.
npm install cbor-web mysql2 uuid @types/uuid
Эта команда устанавливает:
cbor-web
: Для декодирования полезной нагрузки учетных данных mdoc.mysql2
: Клиент MySQL для нашей базы данных.uuid
: Для генерации уникальных строк вызова-ответа.@types/uuid
: Типы TypeScript для библиотеки uuid
.Нашему бэкенду требуется база данных 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 в фоновом режиме.
Наше приложение Next.js структурировано таким образом, чтобы разделить обязанности между фронтендом и бэкендом, хотя они и являются частью одного проекта.
src/app/page.tsx
): Одна страница React, которая
инициирует процесс верификации и отображает результат. Она взаимодействует с Digital
Credential API браузера.src/app/api/verify/...
):
start/route.ts
: Генерирует запрос OpenID4VP и защитный nonce.finish/route.ts
: Получает представление от кошелька (через браузер), проверяет
nonce и декодирует учетные данные.src/lib/
):
database.ts
: Управляет всеми взаимодействиями с базой данных (создание
вызовов-ответов, проверка сессий).crypto.ts
: Обрабатывает декодирование учетных данных mDoc на основе CBOR.Вот диаграмма, иллюстрирующая внутреннюю архитектуру:
Наш фронтенд намеренно сделан легковесным. Его основная задача — служить пользовательским триггером для процесса верификации и взаимодействовать как с нашим бэкендом, так и с нативными возможностями браузера по обработке учетных данных. Он не содержит сложной логики протоколов; все это делегировано.
В частности, фронтенд будет обрабатывать следующее:
/api/verify/start
и получает
структурированную JSON-нагрузку (protocol
, request
, state
), описывающую, что
именно должен представить кошелек.navigator.credentials.get()
,
который отображает нативный QR-код и ожидает ответа кошелька./api/verify/finish
в POST-запросе для окончательной проверки на
стороне сервера.Основная логика находится в функции 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-кода или экран согласия) для
одобрения запроса учетных данных. Без этого веб-сайт потенциально мог бы пытаться
незаметно получить доступ к учетным данным в фоновом режиме, что представляет
значительный риск для конфиденциальности. Требуя посредничества, мы гарантируем, что
пользователь всегда контролирует ситуацию и дает явное согласие на каждую транзакцию.
С готовым UI на React нам теперь нужны два API-маршрута, которые выполняют основную работу на сервере:
/api/verify/start
– создает запрос OpenID4VP, сохраняет одноразовый challenge в
MySQL и передает все обратно в браузер./api/verify/finish
– получает ответ кошелька, проверяет challenge, верифицирует и
декодирует учетные данные и, наконец, возвращает краткий JSON-результат в UI./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, возвращаемый кошельком, чтобы мы могли найти запись в БД.
Файл src/lib/database.ts
оборачивает базовые операции MySQL для challenges и сессий
верификации (вставка, чтение, отметка об использовании). Хранение этой логики в одном
модуле позволяет легко заменить хранилище данных позже.
/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.
Когда верификатор получает учетные данные mdoc от браузера, это строка Base64URL,
содержащая бинарные данные, закодированные в CBOR. Чтобы извлечь фактические утверждения,
эндпоинт finish
выполняет многоэтапный процесс декодирования с использованием
вспомогательных функций из src/lib/crypto.ts
.
Функция 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; }
cbor-web
для декодирования бинарных
данных в структурированный объект JavaScript.Функция 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 для отображения и дальнейшей обработки.
Эндпоинт 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)!».
Теперь у вас есть полный, работающий верификатор, который использует нативные возможности браузера для обработки учетных данных. Вот как запустить его локально и что можно сделать, чтобы превратить его из доказательства концепции в готовое к продакшену приложение.
Клонируйте репозиторий:
git clone https://github.com/corbado/digital-credentials-example.git cd digital-credentials-example
Установите зависимости:
npm install
Запустите базу данных: Убедитесь, что Docker запущен на вашем компьютере, затем запустите контейнер MySQL:
docker-compose up -d
Запустите приложение:
npm run dev
Откройте браузер по адресу http://localhost:3000
, и вы должны увидеть UI
верификатора. Теперь вы можете использовать свой CMWallet, чтобы отсканировать QR-код
и завершить процесс верификации.
Это руководство предоставляет 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.
Этот пример намеренно сфокусирован на основном потоке, опосредованном браузером, чтобы сделать его легким для понимания. Следующие темы считаются выходящими за рамки:
redirect_uri
или динамическая
регистрация клиента.Опираясь на эту основу и внедряя эти следующие шаги, вы можете разработать надежный и безопасный верификатор, способный доверять и проверять цифровые учетные данные в ваших собственных приложениях.
Вот и все! Менее чем в 250 строках TypeScript мы создали сквозной верификатор, который:
В продакшене вы бы заменили проверку-заглушку на полную проверку ISO 18013-5, добавили бы проверку отзыва издателем, ограничение скорости запросов, аудит-логирование и, конечно, сквозное шифрование TLS — но основные строительные блоки остаются точно такими же.
Вот некоторые из ключевых ресурсов, спецификаций и инструментов, используемых или упомянутых в этом руководстве:
Репозиторий проекта:
Ключевые спецификации:
Инструменты:
Библиотеки:
Related Articles
Table of Contents