Digital Credentials API の発行が Chrome 155 でリリース

公開日: 2026 年 10 月 23 日

Chrome 155 以降では、Digital Credentials API の発行がデフォルトで有効になっているため、オリジン トライアル トークンやフラグを使用する必要はありません。Digital Credentials API は、ウェブサイトがユーザーのモバイル デバイスにインストールされているウォレット アプリに接続できるようにするトランスポート層です。発行機能を使用すると、発行者は新しい認証情報(デジタル ID など)をユーザーのウォレット アプリに直接追加できます。

背景

デジタル ID エコシステムには、発行者、検証者(証明書利用者とも呼ばれます)、所有者(ウォレット アプリ)の 3 つの当事者が存在します。ユーザーは多くの場合、プッシュ プロビジョニングを通じてウォレット アプリにデジタル ID を追加します。この場合、ユーザーは発行元のウェブサイトまたはアプリから発行を開始します。たとえば、大学のウェブサイト(発行元)にアクセスした学生は、デジタル学生 ID を好みのウォレット アプリ(ホルダー)に保存できます。

このモデルを成功させるには、発行者がウォレット アプリを呼び出して ID を発行するための安全で信頼性の高い方法が必要です。この API が登場する前は、各カード発行会社が、提携しているウォレット アプリを開く独自の方法を構築する必要がありました。通常は、カスタム URI スキームを使用します。カード発行会社はウォレット アプリごとに個別のメカニズムを維持する必要があり、ユーザーにはウォレット アプリを選択する一貫した方法がありませんでした。

デジタル認証情報の発行の仕組み

このプロセスを効率化するため、Chrome は W3C Digital Credentials API による発行をサポートしています。これは、Chrome 141 でリリースされたプレゼンテーション機能(デジタル認証情報からリクエストされた選択的なデータを検証者と共有する機能)に追加されるものです。発行元のウェブサイトが、発行リクエストを含む digital メンバーを指定して navigator.credentials.create() を呼び出すと、Chrome はリクエストをオペレーティング システム(Android の場合は認証情報マネージャー)に渡します。オペレーティング システムは、ユーザーがウォレット アプリを選択する独自のインターフェースを表示します。この API は、OpenID for Verifiable Credential Issuance(OpenID4VCI)などの業界標準のプロトコルに対応しています。発行では、Android での同一デバイスフローと、パソコンで開始して Android デバイスで終了するクロスデバイス フローの 2 つのフローがサポートされています。

Android での同一デバイス フロー

Android では、Chrome が発行リクエストを Android の認証情報マネージャーに渡します。認証情報マネージャーは、デバイス上の対応するウォレット アプリのリストを表示します。ユーザーがウォレットを選択し、認証情報の追加に同意します。ウォレット アプリは、OpenID4VCI などの発行プロトコルを使用して、発行元から認証情報を取得します。

パソコンでのクロスデバイス フロー

パソコンでは、Chrome に QR コードが表示されます。ユーザーは Android デバイスでコードをスキャンし、そのデバイスでフローを完了します(前述のとおり)。デバイスは Bluetooth を使用して互いに近くにあることを確認するため、Chrome は Bluetooth がオフになっている場合はオンにするようユーザーに求めます。Chrome は、Digital Credentials API のプレゼンテーションに同じクロスデバイス メカニズムを使用します。

API を使用して発行を実装する

発行フローを開始するには、navigator.credentials.create() を呼び出し、digital メンバー内で発行リクエストを渡します。この呼び出しには一時的なユーザー操作が必要なため、クリック ハンドラまたは別のユーザー ジェスチャー ハンドラから呼び出します。次の例では、事前認可コードフローで OpenID4VCI 認証情報のオファーを使用しています。

// Call this function from a user gesture handler, for example, a button's
// click handler. The pre-authorized code is a short-lived, single-use code
// that your server generates for the signed-in user.
async function issueDigitalCredential(preAuthorizedCode) {
  // Check for Digital Credentials API and protocol support.
  if (!window.DigitalCredential ||
      !DigitalCredential.userAgentAllowsProtocol('openid4vci-v1')) {
    // Provide an alternative issuance method.
    showAlternativeIssuanceFlow();
    return;
  }

  // Construct a credential offer according to the OpenID4VCI
  // specification. This example uses the format that the
  // demo CMWallet supports.
  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
      }
    },
    // Metadata that the wallet uses to request and display the credential.
    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',
          // Bind the credential to a key that the wallet generates.
          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' }],
            // The claims that this credential contains. For a full list of
            // standard mDL claims, see the ISO/IEC 18013-5 specification.
            claims: [{
              path: ['org.iso.18013.5.1', 'family_name'],
              display: [{ name: 'Family name', locale: 'en-US' }]
            }
            // ... Add any other claims for your credential here.
            ]
          }
        }
      }
    },
    // Metadata of the authorization server that issues access tokens.
    authorization_server_metadata: {
      issuer: 'https://issuer.example',
      token_endpoint: 'https://issuer.example/openid4vci/token'
    }
  };

  try {
    const credential = await navigator.credentials.create({
      digital: {
        requests: [{
          // The protocol identifier for OpenID4VCI 1.0.
          protocol: 'openid4vci-v1',
          // Pass the offer as an object, not as a JSON string.
          data: credentialOffer
        }]
      }
    });

    // The wallet handled the request. credential.data is an object that
    // contains the wallet's protocol-specific response. It is not the issued
    // credential, and it does not necessarily mean that issuance is complete.
    // Your server knows when the wallet retrieves the credential from your
    // credential endpoint.
    // Implement your custom logic here as needed.
    showIssuanceStatus(credential);
  } catch (error) {
    switch (error.name) {
      case 'NotAllowedError':
        // The user canceled the flow, no wallet can handle the request, or
        // the call does not have transient user activation.
        break;
      case 'NotSupportedError':
        // The browser does not support Digital Credentials API issuance.
        // Fall back to your custom alternative flow logic.
        showAlternativeIssuanceFlow();
        break;
      case 'TypeError':
        // The request is malformed, for example, data is not an object.
        console.error('Invalid issuance request:', error);
        break;
      default:
        // For example, AbortError or NetworkError.
        console.error('Credential issuance failed:', error);
    }
  }
}

const addButton = document.querySelector('#add-to-wallet');
addButton.addEventListener('click', () => {
  // Your server adds a pre-authorized code for the signed-in user
  // to the button's data-pre-authorized-code attribute.
  issueDigitalCredential(addButton.dataset.preAuthorizedCode);
});

この例では、showAlternativeIssuanceFlow() と showIssuanceStatus() は独自のアプリロジックを表しています。

クロスオリジン iframe から create() を呼び出すには、埋め込みページで digital-credentials-create 権限ポリシー機能を許可する必要があります。それ以外の場合、呼び出しは NotAllowedError で拒否されます。

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

オリジン トライアルからの変更

API メソッドとパラメータは、オリジン トライアル以降変更されていません。ただし、Chrome 143 から Chrome 155 の間にリリースされた変更には、コードの更新が必要になるものもあります。次の表に、変更の概要を示します。以降のセクションでは、各変更について説明します。

地域 オリジン トライアル(Chrome 143) Chrome 155
対象 オリジン トライアル トークンまたは 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 以降、digital メンバーを含む create() 呼び出しは、仕様で定められているとおり、一時的なユーザー アクティベーションを消費します。オリジン トライアル中、Chrome はユーザー アクティベーションを確認しましたが、消費しませんでした。

そのため、create() 呼び出しごとに、ボタンのクリックなどのユーザー ジェスチャーが必要になります。ユーザー アクティベーションがない場合、Promise は NotAllowedError で拒否されます。呼び出し後、window.open() と、ユーザー アクティベーションを必要とする他の API は、同じジェスチャーを使用できません。

NotAllowedError でのキャンセル拒否

Chrome 148 以降では、ユーザーがフローをキャンセルした場合(Android でウォレット セレクタを閉じた場合や、パソコンで QR コード ダイアログを閉じた場合など)、create() が返す Promise は NotAllowedError で拒否されます。リクエストを処理できるウォレット アプリがない場合、Promise は NotAllowedError で拒否されます。オリジン トライアル中、これらのケースは NetworkError で拒否されました。

コードで NetworkError をチェックしてキャンセルを検出している場合は、代わりに NotAllowedError をチェックするように更新します。

認識されないプロトコル識別子により警告がトリガーされる

この仕様では、OpenID4VCI 1.0 の単一の発行プロトコル識別子 openid4vci-v1 が定義されています。Chrome 155 は openid4vci-v1 と openid4vci を認識します。openid4vci は仕様に含まれていないため、openid4vci-v1 を使用することをおすすめします。

Chrome は、任意のプロトコル識別子を含むリクエストをプラットフォームに渡します。ただし、Chrome 151 以降では、ページが認識されないプロトコル識別子を使用して create() を呼び出すと、DevTools にサポート終了の警告が表示されます。Chrome では、認識されないプロトコル識別子を含むリクエストをブロックする予定です。ステータスについては、Digital Credential API におけるより詳細なプロトコル フィルタリングのエントリをご覧ください。

この動作をテストするには、chrome://flags/#enable-experimental-web-platform-features を有効にします。このフラグを有効にすると、create() は認識されないプロトコル識別子を含むリクエストを無視し、リクエストが残っていない場合は TypeError で拒否します。また、DigitalCredential.userAgentAllowsProtocol() は認識されたプロトコルに対してのみ true を返します。このフラグを指定しない場合、識別子の形式が正しいことのみがチェックされます。そのため、機能検出を userAgentAllowsProtocol() のみに依存しないでください。例に示すように NotSupportedError を処理します。

不透明なオリジンはブロックされます

Chrome 151 以降、不透明なオリジンを持つドキュメント(allow-same-origin トークンがないサンドボックス化された iframe など)が create() を呼び出すと、NotAllowedError で拒否されます。オリジン トライアル中、Chrome はこれらの呼び出しをブロックしませんでした。

仮想ウォレットでテストする

Chrome 152 以降では、Android デバイスやウォレット アプリを使用せずに発行フローをテストできます。Chrome DevTools Protocol(CDP)の試験運用版 DigitalCredentials.setVirtualWalletBehavior コマンドは、自動テスト用に仕様で定義されている仮想ウォレットを実装します。仮想ウォレットは、指定されたデータで応答したり、ユーザーがフローをキャンセルしたかのようにリクエストを拒否したり、リクエストを保留にしてタイムアウトや中止をテストしたりできます。

次の Puppeteer テストでは、仮想ウォレットを openid4vci-v1 リクエストに応答するように設定し、発行を開始するボタンをクリックします。

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

// Clicking the button provides the user activation that create() requires.
await page.click('#add-to-wallet');

業界での採用

Digital Credentials API の発行機能が導入されています。たとえば、ドイツの金融機関 Sparkasse は、Digital Credentials API を使用して、アカウント所有者が確認済みの年齢認証情報を Google ウォレットに保存できるようにしています。アカウント所有者は、これらの認証情報を使用して、オンラインや対面で年齢を確認できます。

試してみる

Digital Credentials API の発行をテストする前に、次のものがあることを確認してください。

  • Chrome 155 以降に更新してください。
  • サポートされているウォレット アプリ(デモ CMWallet など)を Android デバイスにインストールします。
  • 両方のデバイスで Bluetooth がオンになっている(複数デバイス間のフローのみ)。

クロスデバイス フローをテストするには:

  1. パソコンでデモカード発行会社のサイトを開きます。
  2. [追加] ボタンをクリックします。
  3. Android デバイスで QR コードをスキャンし、画面上の手順に沿って操作します。

同一デバイスのフローをテストするには、Android デバイスの Chrome でデモカード発行会社のサイトを開きます。

デベロッパー向けリソース

Digital Credentials API の発行をウェブサイトに統合するには、次のリソースを使用します。

フィードバックをお寄せください

バグの報告や機能のリクエストを行うには、Chromium バグトラッカーで問題を報告してください。