---
url: 'https://www.corbado.com/ru/blog/kak-sozdat-verifiable-credential-verifier'
title: 'Как создать верификатор цифровых учетных данных (руководство для разработчиков)'
description: 'Узнайте, как создать верификатор цифровых учетных данных с нуля, используя Next.js, OpenID4VP и ISO mDoc. В этом пошаговом руководстве для разработчиков мы покажем, как создать верификатор, который может запрашивать, получать и проверять мобильные водител'
lang: 'ru'
author: 'Amine'
date: '2025-08-20T15:39:39.530Z'
lastModified: '2026-03-25T10:07:14.169Z'
keywords: 'верификатор цифровых учетных данных, руководство по созданию верификатора, создать верификатор'
category: 'Digital Credentials'
---

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

## 1. Введение

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

Это руководство представляет собой практическое пошаговое пособие для разработчиков по
созданию верификатора цифровых учетных данных. Хотя стандарты уже существуют, практических
рекомендаций по их внедрению мало. Это руководство восполняет этот пробел, показывая, как
создать верификатор с использованием встроенного в браузер Digital Credential API,
протокола представления [OpenID4VP](https://www.corbado.com/glossary/open-id-4-vp) и формата учетных данных ISO
[mDoc](https://www.corbado.com/glossary/mdoc) (например, мобильные водительские права).

В результате мы получим простое, но функциональное приложение на
[Next.js](https://www.corbado.com/blog/nextjs-passkeys), которое сможет запрашивать, получать и проверять
цифровые учетные данные из совместимого мобильного кошелька.

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

**Шаг 1: Начальная страница** Пользователь попадает на начальную страницу и нажимает
«Подтвердить с помощью [Digital Identity](https://www.corbado.com/blog/digital-identity-guide)», чтобы начать
процесс.
![Начальная страница для запроса верификации](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/Screenshot_2025_07_25_at_11_00_33_5217b35c96.png)

**Шаг 2: Запрос доверия** Браузер запрашивает у пользователя подтверждение доверия.
Пользователь нажимает «Продолжить», чтобы продолжить.
![Запрос доверия в браузере](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/Screenshot_2025_07_25_at_11_00_39_ba390a8097.png)

**Шаг 3: Сканирование QR-кода** Отображается QR-код, который пользователь сканирует с
помощью совместимого приложения-кошелька.
![QR-код для сканирования](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/Screenshot_2025_07_25_at_11_00_45_3b30669a10.png)

**Шаг 4: Расшифрованные учетные данные** После успешной проверки приложение отображает
расшифрованные данные учетных данных.
![Результат расшифровки учетных данных](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/Screenshot_2025_07_25_at_11_01_36_684f7489cd.png)

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

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

- **Издатель (Issuer):** Доверенный орган (например,
  [государственное](https://www.corbado.com/passkeys-for-public-sector) учреждение, университет или банк),
  который криптографически подписывает и выдает учетные данные пользователю.
- **Владелец (Holder):** Пользователь, который получает учетные данные и надежно хранит их
  в личном цифровом кошельке на своем устройстве.
- **Верификатор (Verifier):** Приложение или сервис, которому необходимо проверить учетные
  данные пользователя.

![Экосистема проверяемых учетных данных W3C](https://www.w3.org/TR/vc-data-model/diagrams/ecosystem.svg)

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

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

Для процветания этой **децентрализованной экосистемы идентификации** роль **верификатора**
абсолютно критична. Они являются стражами этой новой
[инфраструктуры](https://www.corbado.com/passkeys-for-critical-infrastructure) доверия, теми, кто использует
учетные данные и делает их полезными в реальном мире. Как показано на диаграмме ниже,
верификатор замыкает треугольник доверия, запрашивая, получая и проверяя учетные данные от
владельца.

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

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

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

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

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

1. **Базовое понимание цифровых учетных данных и mdoc**
    - Это руководство сосредоточено на формате **ISO mDoc** (например, для мобильных
      водительских прав) и не охватывает другие форматы, такие как W3C Verifiable
      Credentials (VCs). Знакомство с основными концепциями [mdoc](https://www.corbado.com/glossary/mdoc) будет
      полезным.
2. **Docker и Docker Compose**
    - Наш проект использует базу данных [MySQL](https://www.corbado.com/blog/passkey-webauthn-database-guide) в
      контейнере Docker для управления состоянием сессии OIDC. Убедитесь, что у вас
      установлены и запущены оба инструмента.
3. **Выбранный протокол: OpenID4VP**
    - Мы будем использовать протокол **OpenID4VP** (OpenID for Verifiable Presentations)
      для потока обмена учетными данными.
4. **Готовый технологический стек**
    - Используйте **TypeScript** (Node.js) для логики бэкенда.
    - Используйте **Next.js** как для бэкенда (API routes), так и для фронтенда (UI).
    - Ключевые библиотеки: библиотеки для декодирования [CBOR](https://www.corbado.com/glossary/cbor) для
      парсинга [mdoc](https://www.corbado.com/glossary/mdoc) и клиент
      [MySQL](https://www.corbado.com/blog/passkey-webauthn-database-guide).
5. **Тестовые учетные данные и кошелек**
    - Мы будем использовать
      **[CMWallet](https://github.com/digitalcredentialsdev/CMWallet/actions/runs/16407676816/artifacts/3574255220)**
      для [Android](https://www.corbado.com/blog/how-to-enable-passkeys-android), который понимает запросы
      [OpenID4VP](https://www.corbado.com/glossary/open-id-4-vp) и может представлять учетные данные mdoc.
6. **Базовые знания криптографии**
    - Понимание цифровых подписей и концепций открытого/закрытого ключей в контексте mdoc
      и потоков OIDC.

---

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

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

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

| Стандарт / Протокол                                                                       | Описание                                                                                                                                                                                                |
| :---------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **[W3C VC](https://www.w3.org/TR/vc-data-model/)**                                        | Модель данных W3C Verifiable Credentials. Она определяет стандартную структуру для цифровых учетных данных, включая утверждения, метаданные и доказательства.                                           |
| **[SD-JWT](https://datatracker.ietf.org/doc/draft-ietf-oauth-selective-disclosure-jwt/)** | Выборочное раскрытие для JWT. Формат для VC на основе JSON Web Tokens, который позволяет владельцам выборочно раскрывать только определенные утверждения из учетных данных, повышая конфиденциальность. |
| **[ISO mDoc](https://www.iso.org/standard/69084.html)**                                   | ISO/IEC 18013-5. Международный стандарт для мобильных водительских удостоверений (mDL) и других мобильных ID, определяющий структуры данных и протоколы связи для оффлайн- и онлайн-использования.      |
| **OpenID4VP**                                                                             | OpenID for Verifiable Presentations. Совместимый протокол представления, построенный на OAuth 2.0. Он определяет, как верификатор запрашивает учетные данные, а кошелек владельца их представляет.      |

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

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

> **Примечание о рамках:** Хотя мы кратко представляем W3C VC и SD-JWT для более широкого
> контекста, это руководство реализует исключительно учетные данные ISO mDoc через
> [OpenID4VP](https://www.corbado.com/glossary/open-id-4-vp). Учетные данные на основе W3C выходят за рамки этого
> примера.

#### 2.1.1 ISO mDoc (Mobile Document)

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

#### 2.1.2 OpenID4VP (OpenID for Verifiable Presentations)

OpenID4VP — это совместимый протокол для запроса и представления цифровых учетных данных,
построенный на базе [OAuth 2.0](https://www.corbado.com/glossary/oauth2) и OpenID Connect. В этой реализации
OpenID4VP используется для:

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

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

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

#### 2.2.1 Язык: TypeScript

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

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

**Next.js** — наш выбор фреймворка, потому что он обеспечивает бесшовный, интегрированный
опыт для создания [full-stack](https://www.corbado.com/ru/blog/crud-prilozhenie-react-express-mysql) приложений.

- **Для фронтенда:** Мы будем использовать [Next.js](https://www.corbado.com/blog/nextjs-passkeys) с
  [React](https://www.corbado.com/blog/react-passkeys) для создания пользовательского интерфейса, где
  инициируется процесс верификации (например, отображение QR-кода).
- **Для бэкенда:** Мы будем использовать **Next.js API Routes** для создания серверных
  эндпоинтов. Эти эндпоинты отвечают за создание действительных запросов OpenID4VP и за
  работу в качестве `redirect_uri` для безопасного получения и проверки окончательного
  ответа от CMWallet.

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

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

- **next**: Фреймворк [Next.js](https://www.corbado.com/blog/nextjs-passkeys), используемый как для бэкенд
  API-маршрутов, так и для фронтенд UI.
- **react** и **react-dom**: Обеспечивают работу пользовательского интерфейса на
  фронтенде.
- **cbor-web**: Используется для декодирования учетных данных mdoc, закодированных в
  [CBOR](https://www.corbado.com/glossary/cbor), в используемые объекты JavaScript.
- **mysql2**: Обеспечивает подключение к базе данных
  [MySQL](https://www.corbado.com/blog/passkey-webauthn-database-guide) для хранения вызовов-ответов (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](https://github.com/digitalcredentialsdev/CMWallet/actions/runs/16407676816/artifacts/3574255220)**,
надежный тестовый кошелек, совместимый с OpenID4VP, для
[Android](https://www.corbado.com/blog/how-to-enable-passkeys-android).

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

1. **Загрузите APK-файл**, используя ссылку выше, прямо на ваше устройство
   [Android](https://www.corbado.com/blog/how-to-enable-passkeys-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](https://www.corbado.com/glossary/microcredentials) или учетных данных на основе 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](https://www.corbado.com/glossary/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-код,
  обрабатывает запрос, получает согласие пользователя и отправляет подписанный ответ
  обратно в браузер.

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

![Процесс верификации с использованием Digital Credentials API браузера](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_24_233623_1b6ed9b957.svg)

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

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».

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

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

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

> **Следите за нами или используйте готовый код**
>
> Сейчас мы пошагово пройдем через настройку и реализацию кода. Если вы предпочитаете
> сразу перейти к готовому продукту, вы можете клонировать полный проект из нашего
> репозитория на GitHub и запустить его локально.
>
> ```bash
> git clone https://github.com/corbado/digital-credentials-example.git
> ```

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

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

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

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

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

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

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

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

```bash
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` со следующим
содержимым:

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

Эта настройка Docker Compose также требует скрипта инициализации SQL. Создайте каталог с
именем `sql` и внутри него файл с именем `init.sql` со следующим содержимым для настройки
необходимых таблиц:

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

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

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

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

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

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

- **Фронтенд (`src/app/page.tsx`):** Одна страница [React](https://www.corbado.com/blog/react-passkeys), которая
  инициирует процесс верификации и отображает результат. Она взаимодействует с 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.

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

![Внутренняя архитектура NextJS](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_25_091202_f96ccb049f.svg)

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

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

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

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

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

```typescript
// 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](https://www.corbado.com/blog/react-passkeys)
для управления состоянием и рендеринга UI, который вы можете посмотреть в
[репозитории на GitHub](https://github.com/corbado/digital-credentials-example).

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

Вы могли заметить, что наш вызов `navigator.credentials.get()` выглядит иначе, чем в более
простых примерах. Это потому, что мы строго придерживаемся официальной
[спецификации W3C Digital Credentials API](https://www.w3.org/TR/digital-credentials/#the-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

```typescript
// 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`: Валидация и декодирование представления

```typescript
// 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` обрабатывает преобразование из закодированной строки в
используемый объект:

```typescript
// 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 для
извлечения фактических утверждений из соответствующих пространств имен:

```typescript
// 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, например:

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

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

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

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

```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. **Клонируйте репозиторий:**

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

2. **Установите зависимости:**

    ```bash
    npm install
    ```

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

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

4. **Запустите приложение:**

    ```bash
    npm run dev
    ```

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

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

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

- **Полная криптографическая валидация:** Текущая реализация использует проверку-заглушку
  (`mdocToken.length > 0`). В реальном сценарии вы должны выполнять полную
  криптографическую валидацию подписи mdoc с использованием открытого ключа издателя
  (например, путем разрешения их DID или получения их сертификата открытого ключа). Для
  стандартов разрешения DID обратитесь к
  [спецификации W3C DID Resolution](https://www.w3.org/TR/did-resolution/).

- **Проверка отзыва издателем:** Учетные данные могут быть отозваны издателем до истечения
  срока их действия. Продакшен-верификатор должен проверять статус учетных данных,
  запрашивая список отзыва или эндпоинт статуса, предоставленный издателем.
  [W3C Verifiable Credentials Status List](https://www.w3.org/TR/vc-bitstring-status-list/)
  предоставляет стандарт для списков отзыва учетных данных.

- **Надежная обработка ошибок и безопасность:** Добавьте комплексную обработку ошибок,
  валидацию ввода, ограничение скорости запросов на API-эндпоинтах и убедитесь, что все
  коммуникации происходят по HTTPS (TLS) для защиты данных при передаче.
  [OWASP API Security Guidelines](https://owasp.org/www-project-api-security/)
  предоставляют комплексные рекомендации по безопасности API.

- **Поддержка нескольких типов учетных данных:** Расширьте логику для обработки различных
  значений `doctype` и форматов учетных данных, если вы ожидаете получать не только
  европейские учетные данные [Digital Identity](https://www.corbado.com/blog/digital-identity-guide) (EUDI) PID.
  [W3C Verifiable Credentials Data Model](https://www.w3.org/TR/vc-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](https://www.corbado.com/glossary/iso-18013-5), добавили бы проверку отзыва издателем, ограничение
скорости запросов, аудит-логирование и, конечно, сквозное шифрование TLS — но основные
строительные блоки остаются точно такими же.

## Ресурсы

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

- **Репозиторий проекта:**
    - [Полный исходный код на GitHub](https://github.com/corbado/digital-credentials-example)

- **Ключевые спецификации:**
    - [W3C Verifiable Credentials Data Model](https://www.w3.org/TR/vc-data-model/):
      Фундаментальный стандарт для VC.
    - [OpenID for Verifiable Presentations (OpenID4VP)](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html):
      Протокол представления, используемый для обмена учетными данными.
    - [ISO/IEC 18013-5 (mDoc)](https://www.iso.org/standard/69084.html): Международный
      стандарт для мобильных водительских удостоверений (mDL).
    - [W3C Digital Credentials API](https://www.w3.org/TR/digital-credentials/#the-digital-credentials-api):
      API браузера, используемый для запроса учетных данных из кошелька.

- **Инструменты:**
    - [CMWallet для Android](https://github.com/digitalcredentialsdev/CMWallet/actions/runs/16407676816/artifacts/3574255220):
      Тестовый кошелек, используемый в этом руководстве.

- **Библиотеки:**
    - Next.js: Фреймворк React для создания фронтенда и бэкенда.
    - [cbor-web](https://github.com/hildjj/cbor-web): Для декодирования учетных данных
      mdoc, закодированных в CBOR.
    - [mysql2](https://github.com/sidorares/node-mysql2): Клиент MySQL для
      [Node.js](https://www.corbado.com/blog/nodejs-passkeys).
