Эта статья перевод оригинальной статьи «Digital Credentials API issuance ships in Chrome 155».

Также я веду телеграм канал «Frontend по‑флотски», где рассказываю про интересные вещи из мира разработки интерфейсов и AI.

Вступление

В Chrome 155 и более поздних версиях возможность добавлять цифровые подтверждения в кошелёк через Digital Credentials API включена по умолчанию. Для её использования больше не нужен токен origin trial или флаг. Digital Credentials API работает как транспортный слой между сайтом и приложением-кошельком на мобильном устройстве пользователя. С его помощью организация может добавить в кошелёк цифровое подтверждение, например удостоверение личности.

Предыстория

В экосистеме цифровой идентификации участвуют три стороны: эмитенты, проверяющие стороны (relying parties) и держатели, то есть приложения-кошельки. Часто пользователь добавляет удостоверение личности в кошелёк через push provisioning: начинает процесс на сайте или в приложении эмитента. Например, студент может сохранить цифровой студенческий билет с сайта университета в выбранный кошелёк.

Для этого эмитенту нужен безопасный и надёжный способ вызвать приложение-кошелёк. До появления API каждый эмитент самостоятельно открывал приложения партнёров, обычно с помощью собственных схем URI. Приходилось поддерживать отдельный механизм для каждого кошелька, а у пользователя не было единого способа выбрать приложение.

Как добавить подтверждение в кошелёк

Chrome поддерживает добавление подтверждений в кошелёк через W3C Digital Credentials API. В Chrome 141 также появилась возможность предъявлять цифровые подтверждения: пользователь может выборочно передать проверяющей стороне запрошенные данные.

Когда сайт эмитента вызывает navigator.credentials.create() с полем digital, содержащим запрос на выпуск, Chrome передаёт запрос операционной системе. На Android это Credential Manager. Система показывает собственный интерфейс, в котором пользователь выбирает кошелёк. API работает со стандартными отраслевыми протоколами, например OpenID for Verifiable Credential Issuance (OpenID4VCI). Поддерживаются два сценария: на одном Android-устройстве и между устройствами, когда процесс начинается на компьютере, а заканчивается на Android.

На одном устройстве Android

Chrome передаёт запрос в Android Credential Manager, который показывает совместимые кошельки на устройстве. Пользователь выбирает приложение и соглашается добавить подтверждение. Затем кошелёк получает его от эмитента по соответствующему протоколу, например OpenID4VCI.

Пользователь сохраняет цифровое подтверждение с сайта эмитента в кошелёк на том же устройстве Android.

С компьютера на Android

На компьютере Chrome показывает QR-код. Пользователь сканирует его устройством Android и завершает процедуру на нём, как описано выше. Устройства проверяют близость друг к другу через Bluetooth, поэтому Chrome предлагает включить его, если он выключен. Тот же механизм между устройствами Chrome использует при предъявлении подтверждений через Digital Credentials API.

Пользователь сканирует QR-код, чтобы связать Chrome на компьютере с устройством Android. Затем Android Credential Manager показывает кошельки, которые могут сохранить подтверждение.

Добавление подтверждения через API

Чтобы начать добавление подтверждения в кошелёк, вызовите navigator.credentials.create() и передайте запросы в поле digital. Вызов должен находиться в обработчике нажатия или другого действия пользователя, поскольку ему необходима временная активация пользователя. В примере ниже используется предложение о выпуске подтверждения OpenID4VCI со сценарием предварительно авторизованного кода.

Формат data определяет протокол выпуска, а не Digital Credentials API. Детали интеграции OpenID4VCI с API могут измениться; обсуждение ведётся в OpenID4VCI issue #410. Пример использует формат, поддерживаемый демонстрационным CMWallet. В этом формате предложение о выпуске подтверждения содержит метаданные эмитента и сервера авторизации.

// Вызывайте эту функцию из обработчика действия пользователя, например
// нажатия кнопки. Предварительно авторизованный код одноразовый и действует
// недолго; сервер генерирует его для вошедшего пользователя.
async function issueDigitalCredential(preAuthorizedCode) {
  // Проверьте поддержку Digital Credentials API и протокола.
  if (!window.DigitalCredential ||
      !DigitalCredential.userAgentAllowsProtocol('openid4vci-v1')) {
    // Предусмотрите альтернативный способ выпуска подтверждения.
    showAlternativeIssuanceFlow();
    return;
  }

  // Сформируйте предложение о выпуске подтверждения по спецификации
  // OpenID4VCI. В примере используется формат, поддерживаемый
  // демонстрационным CMWallet.
  const credentialOffer = {
    credential_issuer: 'https://issuer.example',
    credential_configuration_ids: ['org.iso.18013.5.1.mDL'],
    grants: {
      'urn:ietf:params:oauth:grant-type:pre-authorized_code': {
        'pre-authorized_code': preAuthorizedCode
      }
    },
    // Метаданные, которые кошелёк использует для запроса и отображения подтверждения.
    credential_issuer_metadata: {
      credential_issuer: 'https://issuer.example',
      credential_endpoint: 'https://issuer.example/openid4vci/credential',
      nonce_endpoint: 'https://issuer.example/openid4vci/nonce',
      credential_configurations_supported: {
        'org.iso.18013.5.1.mDL': {
          format: 'mso_mdoc',
          doctype: 'org.iso.18013.5.1.mDL',
          // Привяжите подтверждение к ключу, который создаёт кошелёк.
          cryptographic_binding_methods_supported: ['cose_key'],
          proof_types_supported: {
            jwt: { proof_signing_alg_values_supported: ['ES256'] }
          },
          credential_metadata: {
            display: [{ name: 'Mobile driving license', locale: 'en-US' }],
            // Данные, содержащиеся в подтверждении. Полный список стандартных
            // полей mDL приведён в спецификации ISO/IEC 18013-5.
            claims: [{
              path: ['org.iso.18013.5.1', 'family_name'],
              display: [{ name: 'Family name', locale: 'en-US' }]
            }
            // ... Добавьте здесь остальные поля подтверждения.
            ]
          }
        }
      }
    },
    // Метаданные сервера авторизации, который выдаёт токены доступа.
    authorization_server_metadata: {
      issuer: 'https://issuer.example',
      token_endpoint: 'https://issuer.example/openid4vci/token'
    }
  };

  try {
    const credential = await navigator.credentials.create({
      digital: {
        requests: [{
          // Идентификатор протокола для OpenID4VCI 1.0.
          protocol: 'openid4vci-v1',
          // Передайте предложение как объект, а не как строку JSON.
          data: credentialOffer
        }]
      }
    });

    // Кошелёк обработал запрос. credential.data — объект с ответом кошелька
    // в формате протокола. Это не выпущенное подтверждение, и ответ не
    // обязательно означает, что выпуск завершён. Ваш сервер узнаёт, когда
    // кошелёк получает подтверждение из вашей конечной точки.
    // При необходимости реализуйте здесь собственную логику.
    showIssuanceStatus(credential);
  } catch (error) {
    switch (error.name) {
      case 'NotAllowedError':
        // Пользователь отменил процедуру, ни один кошелёк не может обработать
        // запрос или вызов выполнен без временной активации пользователя.
        break;
      case 'NotSupportedError':
        // Браузер не поддерживает выпуск через Digital Credentials API.
        // Используйте собственную логику альтернативного сценария.
        showAlternativeIssuanceFlow();
        break;
      case 'TypeError':
        // Запрос некорректен: например, data не является объектом.
        console.error('Invalid issuance request:', error);
        break;
      default:
        // Например, AbortError или NetworkError.
        console.error('Credential issuance failed:', error);
    }
  }
}

const addButton = document.querySelector('#add-to-wallet');
addButton.addEventListener('click', () => {
  // Сервер добавляет предварительно авторизованный код вошедшего пользователя
  // в атрибут кнопки data-pre-authorized-code.
  issueDigitalCredential(addButton.dataset.preAuthorizedCode);
});

Здесь showAlternativeIssuanceFlow() и showIssuanceStatus() обозначают собственную логику приложения.

В Chrome 141–154 метод userAgentAllowsProtocol() возвращает true для openid4vci-v1, даже когда возможность выпуска подтверждений не включена. В таком случае create() отклоняет промис с NotSupportedError. Обработайте эту ошибку и используйте альтернативный способ выпуска.

Чтобы вызвать create() из iframe другого источника, встраивающая страница должна разрешить возможность digital-credentials-create через Permissions Policy. Иначе вызов завершится ошибкой NotAllowedError:

<iframe src="https://issuer.example" allow="digital-credentials-create">
</iframe>

Что изменилось после origin trial

Методы и параметры API с периода origin trial не изменились. Однако между Chrome 143 и Chrome 155 появились изменения, которые могут потребовать обновления кода.

Область

Origin trial (Chrome 143)

Chrome 155

Доступность

Токен origin trial или флаг chrome://flags/#web-identity-digital-credentials-creation

Включено по умолчанию

Активация пользователя

Требуется, но не расходуется

Требуется и расходуется каждым вызовом create() (Chrome 146)

Ошибки отмены

NetworkError

NotAllowedError (Chrome 148)

Идентификаторы протоколов

Chrome передаёт любой идентификатор операционной системе

Неизвестные идентификаторы вызывают предупреждение об устаревании (Chrome 151)

Непрозрачные источники

Разрешены

Блокируются с NotAllowedError (Chrome 151)

Автоматизированное тестирование

Встроенной поддержки нет

Виртуальный кошелёк в Chrome DevTools Protocol (Chrome 152)

Активация пользователя расходуется

Начиная с Chrome 146 вызов create() с полем digital расходует временную активацию пользователя, как требует спецификация. Во время origin trial Chrome проверял активацию, но не расходовал её.

Поэтому каждому вызову create() нужно отдельное действие пользователя, например нажатие кнопки. Без активации промис отклоняется с NotAllowedError. После вызова window.open() и другие API, которым нужна активация, уже не смогут использовать то же действие.

При отмене возвращается NotAllowedError

Начиная с Chrome 148 промис, возвращённый create(), отклоняется с NotAllowedError, если пользователь отменил процедуру, например закрыл выбор кошелька на Android или окно с QR-кодом на компьютере. Та же ошибка возникает, когда ни один кошелёк не может обработать запрос. Во время origin trial в этих случаях возвращался NetworkError.

Если код определяет отмену по NetworkError, измените проверку на NotAllowedError.

Неизвестные идентификаторы протоколов вызывают предупреждение

Спецификация определяет один идентификатор протокола выпуска для OpenID4VCI 1.0: openid4vci-v1. Chrome 155 распознаёт как openid4vci-v1, так и openid4vci. Поскольку второй идентификатор отсутствует в спецификации, автор рекомендует использовать openid4vci-v1.

Chrome передаёт платформе запросы с любым идентификатором протокола. Однако с Chrome 151 DevTools показывает предупреждение об устаревании, когда страница вызывает create() с неизвестным идентификатором. Chrome планирует блокировать такие запросы; статус можно посмотреть на странице ChromeStatus.

Чтобы проверить это поведение, включите chrome://flags/#enable-experimental-web-platform-features. С включённым флагом create() игнорирует запросы с неизвестными идентификаторами и возвращает TypeError, если запросов не осталось. DigitalCredential.userAgentAllowsProtocol() при этом возвращает true только для известных протоколов. Без флага метод проверяет лишь корректность формы идентификатора. Поэтому не полагайтесь только на userAgentAllowsProtocol() при проверке поддержки: обработайте NotSupportedError, как в примере.

Непрозрачные источники блокируются

С Chrome 151 вызов create() отклоняется с NotAllowedError, если его делает документ с непрозрачным источником, например изолированный iframe без токена allow-same-origin. Во время origin trial Chrome такие вызовы не блокировал.

Тестирование с виртуальным кошельком

С Chrome 152 можно проверить добавление подтверждения без устройства Android и приложения-кошелька. Экспериментальная команда DigitalCredentials.setVirtualWalletBehavior в Chrome DevTools Protocol (CDP) реализует виртуальный кошелёк, предусмотренный спецификацией для автоматизированного тестирования. Он может ответить заданными данными, отклонить запрос так, словно пользователь отменил процедуру, или оставить запрос ожидающим для проверки тайм-аутов и прерываний.

В следующем тесте Puppeteer виртуальный кошелёк настраивается на ответ запросам openid4vci-v1, после чего тест нажимает кнопку выпуска:

const client = await page.createCDPSession();
await client.send('DigitalCredentials.setVirtualWalletBehavior', {
  action: 'respond',
  protocol: 'openid4vci-v1',
  response: {}
});

// Нажатие кнопки даёт активацию пользователя, необходимую для create().
await page.click('#add-to-wallet');

Использование в индустрии

Добавление подтверждений через Digital Credentials API уже применяют на практике. Например, немецкая финансовая организация Sparkasse использует API, чтобы клиенты могли сохранять цифровые подтверждения возраста в Google Wallet. Затем ими можно подтвердить возраст онлайн и при личном обращении.

Как попробовать

Чтобы проверить добавление подтверждений через Digital Credentials API, понадобятся:

  • Chrome 155 или новее;

  • поддерживаемый кошелёк на Android, например демонстрационный CMWallet;

  • Bluetooth на обоих устройствах, если используется сценарий между устройствами.

Чтобы проверить сценарий между устройствами:

  1. Откройте на компьютере демонстрационный сайт эмитента.

  2. Нажмите кнопку Add.

  3. Отсканируйте QR-код устройством Android и следуйте инструкциям на экране.

Для проверки на одном устройстве откройте демонстрационный сайт эмитента в Chrome на Android.

Материалы для разработчиков

Для добавления подтверждений через Digital Credentials API на сайт пригодятся следующие материалы:

Обратная связь

Сообщить об ошибке или предложить новую возможность можно в системе отслеживания ошибок Chromium.

Комментарии (0)