OpenID for Verifiable Credential Issuance (openid/OpenID4VCI)

元ファイル (GitHub)

一言で言うと: OpenID4VCI は、ウォレットが Credential Issuer から Verifiable Credential を受け取るための、OAuth で保護された API である。1.0 は 2025 年 9 月から OpenID Final Specification で、openid/OpenID4VCI リポジトリでは現在、1.0 のエラッタ版ドラフト、Interactive Authorization を加えた 1.1 ドラフト、新しい Server to Server ドラフトの 3 つを編集している。

概要

OpenID4VCI は「Verifiable Credential を発行するための、OAuth で保護された API」を定める。対象のフォーマットは問わず、SD-JWT VC、ISO mdoc、W3C VCDM を含む(1.0 Section 1)。Credential Issuer は OAuth 2.0 の Resource Server として振る舞う。ウォレットは OAuth クライアントとして Authorization Server からアクセストークンを取り、それを使って Credential Endpoint を呼ぶ(1.0 Section 3.2)。OAuth への追加は、Pre-Authorized Code グラント、authorization details の openid_credential 型、issuer_state パラメーター、ウォレット側の credential_offer_endpoint である(1.0 Section 3.2)。

OpenID Foundation の会員投票で OpenID4VCI 1.0 は Final Specification として承認された。告知の日付は 2025 年 9 月 16 日で、賛成 102、反対 1、棄権 12 だった。リポジトリ(コミット 21ffd6c、2026 年 10 月 27 日)には編集中のドラフトが 3 つあり、どれも OpenID Digital Credentials Protocols ワーキンググループのものである。

  • 1.0/: 「1.0 incorporating errata set 1」、リビジョン -19。Document History が Final からの変更として挙げるのは、Transaction Code の推測攻撃に関するセキュリティ上の考慮事項の追加と、メタデータ URL を組み立てる際に末尾の / を取り除く規則の復活の 2 点である。1.0/ の git 履歴を見ると、ほかにも小さな修正が入っている。遅延発行の 202 の例の reason phrase(#722)、鍵束縛のないクレデンシャルについての Section 14.2 の段落(#714)、誤字の修正(#731)、エラッタ版のタイトルと新しい Section 1.1「Errata revisions」(#662)である。
  • 1.1/: リビジョン -01。「1.0 Final と同じ本文」から始めて拡張している。最大の追加は Interactive Authorization(Section 6)である。この本文は Final の前に 1.0 ドラフトから外されていた。1.1 はまずそれを戻し、その後 OAuth 2.0 for First-Party Applications(FPA)のプロファイルとして作り直した(Document History、#736)。
  • s2s/: 「Server to Server」、リビジョン -01。Wallet Server と Issuer Server の間の API で、最初のドラフトは 2026 年 8 月 4 日にマージされた(#753)。

仕組み

図は Authorization Code Flow(1.0 Section 3.4)と Pre-Authorized Code Flow(Section 3.5)に、任意の Nonce Endpoint(Section 7)と遅延発行(Sections 8.3 と 9)を加えたものである。

図
sequenceDiagram
    participant W as ウォレット
    participant AS as Authorization Server
    participant CI as Credential Issuer
    opt 発行者が開始する場合
        CI->>W: Credential Offer (credential_issuer, credential_configuration_ids, grants)
    end
    W->>CI: GET /.well-known/openid-credential-issuer
    CI-->>W: Credential Issuer Metadata
    alt Authorization Code Flow
        W->>AS: Authorization Request (authorization_details または scope, issuer_state)
        AS-->>W: Authorization Response (code)
        W->>AS: Token Request (code)
    else Pre-Authorized Code Flow
        W->>AS: Token Request (pre-authorized_code, tx_code)
    end
    AS-->>W: Token Response (access_token)
    opt 発行者が Nonce Endpoint を持つ場合
        W->>CI: Nonce Request (POST、アクセストークン不要)
        CI-->>W: c_nonce
    end
    W->>CI: Credential Request (アクセストークン, proofs)
    alt すぐに発行できる
        CI-->>W: 200、credentials
    else 遅延発行
        CI-->>W: 202、transaction_id と interval
        W->>CI: Deferred Credential Request (transaction_id)
        CI-->>W: 200、credentials
    end
  1. 発行者が開始する場合、Credential Issuer は Credential Offer を値渡し(credential_offer)か参照渡し(credential_offer_uri)で送る。Offer は QR コードにして渡すこともある。中身は credential_issuer と credential_configuration_ids、任意で grants である(1.0 Sections 4.1 と 4.1.1)。ウォレットが開始する場合は Offer がない。
  2. ウォレットは Credential Issuer Identifier のホストとパスの間に /.well-known/openid-credential-issuer を挟んだ URL から Credential Issuer Metadata を取得する(1.0 Section 12.2.2)。
  3. Authorization Code Flow では、ウォレットは openid_credential 型の authorization_details かクレデンシャル用の scope を付けて Authorization Request を送り、Offer に issuer_state があればそれも付ける。受け取ったコードは Token Endpoint で交換する(Sections 5 と 6)。Pre-Authorized Code Flow では Authorization Endpoint を通らず、pre-authorized_code を送る。Offer が Transaction Code(tx_code)を求めていればそれも送る(Section 3.5)。
  4. Credential Issuer が Nonce Endpoint を持っていれば、ウォレットはそこから c_nonce を取る。このエンドポイントは保護されたリソースではない。proof に c_nonce を求める発行者は、このエンドポイントを提供しなければならない(MUST)(Section 7)。
  5. ウォレットはアクセストークンと、鍵バインディングが必要なら proofs を付けて Credential Request を送る(Section 8.2)。発行者は 200 で credentials を返す。すぐに発行できないときは、202 で transaction_id と interval を返さなければならない(MUST)(Section 8.3)。ウォレットはその transaction_id で、任意の Deferred Credential Endpoint に後から問い合わせる(Section 9)。

1.1 では、認可コードを得る 3 つ目の方法が加わる。ウォレットはブラウザを介さず、Authorization Challenge Endpoint(メタデータでは authorization_challenge_endpoint)を直接呼ぶ。Authorization Server は 403 で insufficient_authorization と interaction_type_required を返すことができ、プレゼンテーションを求める場合は auth_session と openid4vp_request も付ける。ウォレットは OpenID4VP で別のクレデンシャルを提示し、auth_session を付けて再送する。これをコードが返るまで繰り返す(1.1 Sections 6 から 6.2.1.1)。

ユースケース

1.1 ドラフトの Appendix H は 6 つのユースケースを挙げる。同一端末での Credential Offer、エンドユーザーが事前に情報を提出したうえでの別端末での Offer、別端末かつ遅延発行、プレゼンテーション中にウォレットが開始する発行、その発行中にさらに別のクレデンシャルの提示を求められる場合、インストール直後にウォレットが開始する発行である。

  • ホルダー側を実装するウォレットは、Sections 3 から 9 と、SD-JWT VC、mdoc、W3C VC 用の Appendix A の Credential Format Profiles に従う。
  • 人手の審査が要る発行者は、transaction_id を返し、Deferred Credential Endpoint を用意する(Sections 8.3 と 9)。
  • 発行前に別のクレデンシャルを確認したいが、利用者をブラウザに移したくない発行者は、1.1 の Interactive Authorization を使う(Section 6、urn:openid:dcp:ia:openid4vp_presentation)。
  • クーポンやチケットのようなベアラー型クレデンシャルの発行者は cryptographic_binding_methods_supported を省き、その結果 proof_types_supported もなくなるので、ウォレットは proofs を送らない(1.1 Section 15.2、#714 で追加)。
  • ウォレットアプリと発行者の間に自前のサーバーを置くウォレット提供者は、s2s ドラフトを使う。Wallet Server と Issuer Server は mTLS で相互認証し、Wallet Client が発行者向けに署名・暗号化したデータは Wallet Server からは見えない(s2s Abstract、Sections 4、5.1、5.5)。

なぜ今重要か

  • きっかけ: subject.json に理由はない(ai_note が空)。openid トラックで、まだディープダイブのない候補の先頭だった。
  • 根拠: 2026 年 6 月から 10 月にかけて、FPA を土台にした Interactive Authorization(#736、6 月 24 日)、最初の s2s ドラフト(#753、8 月 4 日)、Interactive Authorization のセッション束縛の規則(#706、#797、#803)、Credential Dataset のパラメーター(#472、10 月 15 日)がマージされた。オープンな #687 は、1.1 を Final にするまでの道筋についての個人的な提案で、実装者向けドラフトの WG ラストコールと形式的なセキュリティ分析を並行して始め、その後に Final の WG ラストコールを行う案を示している。
  • 想定される影響: 今実装すべき版は 1.0 Final である。1.1 も追うウォレットと発行者は、通信内容が変わる箇所に備える必要がある。Interactive Authorization のエンドポイントとパラメーター、トークンエラーの invalid_tx_code、Credential Response の credential_dataset_id と credential_dataset_tag である。

変更点

以下はすべて、各プルリクエストの差分と 21ffd6c 時点のドラフト本文で確認した。

  • 1.0 エラッタ(-19): Section 12.2.2 に末尾の / の規則が戻った(#775、2026 年 9 月 28 日マージ)。Section 13.6.3「Transaction Code Guessing」も加わった。Transaction Code の失敗回数を制限すべきこと(SHOULD)、上限を超えたら Pre-Authorized Code を無効にしてその後の Token Request を拒否すべきこと(SHOULD)、コードが無効だと分かるエラーを返すべきこと(SHOULD)、無効になった Pre-Authorized Code には invalid_grant を返すべきこと(SHOULD)を定めている(#686)。
  • 1.1、Interactive Authorization(Section 6): FPA のプロファイルとして作り直され、Authorization Challenge Endpoint を使う(#736)。応答は FPA draft -04 に合わせて 401 ではなく 403 Forbidden になった(#781)。インタラクション種別は URN になり(#712)、今の名前は urn:openid:dcp:ia:openid4vp_presentation や urn:openid:dcp:ia:auth_via_web である(#736)。expected_origins がある場合は、Authorization Challenge Endpoint から導いた Origin だけを含めなければならない(MUST)(Section 6.2.1.1)。期待する URL を使う案の #695 はクローズされた。
  • 1.1、auth_session(Section 6.2.1): 「インタラクション種別が指定する場合に REQUIRED」となり、独自の種別はこれを必須にするか、リクエストを結び付ける別の方法を定めるかのどちらかにする(#706)。ウォレットは最後に受け取った値を送らなければならない(MUST)。#710 は、これを進行中の認可リクエストの流れの中に限定した。DPoP を使う場合、AS は auth_session の値を DPoP 公開鍵と結び付けなければならない(MUST)(#803)。
  • 1.1、auth_via_web(Section 6.2.1.2): ウォレットは request_uri の値を一度だけ使わなければならない(MUST)。AS は一回限りとして扱うべきだが(SHOULD)、再読み込みによる重複は受け入れてもよい(MAY)(#704)。並行セッションを持つウォレットは state を送らなければならない(MUST)(#797)。
  • 1.1、Credential Response(Section 9.3): 任意の credential_metadata(#721)と、推奨の credential_dataset_id と credential_dataset_tag が加わった。後者によって、ウォレットは保存済みのクレデンシャルが置き換わったかを判断できる(#472、Section 15.4)。
  • 1.1、Token Error Response(Section 7.3): Transaction Code の誤りには invalid_tx_code を返す。1.0 では invalid_grant である(1.0 Section 6.3、#686)。
  • 1.0 と 1.1: 202 の例が 202 OK から 202 Accepted に直った(#722)。鍵バインディングが不要な発行者は cryptographic_binding_methods_supported を省く(#714)。
  • s2s: 最初のドラフト(#753)、ビルドルール(#799)、バージョン表の修正(#798)、全体の snake_case 化(#809、2026 年 10 月 27 日マージ)。
  • オープン: Interaction Required Response の本文で error_code を error に直す(#819)。s2s の識別子モデルの名前変更で、Session Id を Wallet Id に、Credential Instance Id を Credential Id にする(#810)。s2s の遅延クレデンシャルで、Transaction Id と専用エンドポイントを加える(#816、#810 が前提)。Credential Offer の issuer_redirect_uri と、その許可リストになるメタデータ expected_redirect_uris(#759)。key attestation の nonce と di_vp の challenge の条件(#678)。
  • マージされずにクローズ: #695(上記)、#699(JOSE を使う VCDM 2 のフォーマットプロファイル)、#716(IA の応答のステータスコード要件)、#723(#759 に置き換え)。

互換性について。#819 は 1 語の修正である。例と FPA draft -04 Section 5.2.2 はすでに error を使っているので、直すのは本文の記述で、通信内容は変わらない。s2s の名前変更(#809、#810)は JSON の名前を変えるが、s2s はまだリビジョンが 1 つしかなく、安定版もない。運用中の 1.0 発行者の挙動を変えるのは #775 である。Final の本文には末尾の / の規則がない(#744 は「silent」と書いている)ので、文字どおり読めば https://issuer.example.com/tenant/ は .../.well-known/openid-credential-issuer/tenant/ になり、エラッタの本文では .../tenant になる。元になった issue(#744)は、これを「technically ... a breaking change」と書いている。

技術分析

  • 脅威モデル: 1.0 の Security Considerations は、Credential Offer の改ざん(Section 13.5)、Pre-Authorized Code のリプレイと Transaction Code のフィッシングや推測(Section 13.6)、c_nonce で防ぐ鍵 proof のリプレイ(Section 13.8)、アクセストークンの保護(Section 13.10)を扱う。ネイティブアプリのウォレットでは、送信者制約の方式として DPoP を推奨している(RECOMMENDED)(Section 13.2)。1.1 は Interactive Authorization 固有の脅威を 2 つ加える。1 つはセッション固定攻撃で、AS は OpenID4VP の nonce を auth_session と結び付けなければならない(MUST)(Section 6.2.1.4)。もう 1 つは、悪意のある AS が別の AS のプレゼンテーション要求を転送する攻撃で、expected_origins とプレゼンテーションの audience の束縛で防ぐ(Section 6.2.1.5)。
  • プロトコルフロー: すべてのフォーマットに同じフローを使い、フォーマット固有の部分は Appendix A にまとめている。バッチ発行は複数の proof を付けた 1 回の Credential Request で、フォーマットとデータセットは同じで鍵だけが違うクレデンシャルを受け取る(1.0 Section 3.3.2)。proof の種類は jwt、di_vp、attestation である(Appendix F)。
  • 相互運用性への含意: 実装が食い違いうるのは、末尾が / の識別子のメタデータ URL(1.0 Final とエラッタの違い)、Transaction Code の誤りに返すトークンエラー(1.0 は invalid_grant、1.1 は invalid_tx_code)、key attestation に nonce を入れるかどうか(#678 がオープン)である。1.0 は Final になっていない依存仕様の版も固定している。SD-JWT VC -11、Attestation-Based Client Authentication -07、Token Status List -12、OpenID Federation -43 で、1.1 はこれに FPA -04 を加える(1.0 Section 14.7、1.1 Section 15.9)。

未解決の問題

  • #775 のメタデータ URL の規則は 1.0 Final のエラッタとして公開されるのか。それまでの間、末尾が / の識別子を持つ発行者は両方の URL をどう提供すべきか。
  • key attestation の nonce を WG はどう決着させるのか。#678 は 1.0 の本文で「at the Issuer's discretion」、1.1 の本文で「left to the Wallet」としている。
  • #759 で、攻撃者が Credential Offer を書き換えられる前提のもと、expected_redirect_uris との一致だけでウォレットは issuer_redirect_uri を信頼してよいのか。
  • s2s の遅延クレデンシャル(#816)は、現在の s2s ドラフトがすでに返す PENDING 状態とどう関係するのか。
  • #687 が次の段階とする 1.1 の実装者向けドラフトはいつ出るのか。

実験

  • 1.0 Final と 1.0/ のエラッタ版ドラフトをビルドして全体を比較し、Document History の項目と #722、#714、#731、#662 以外に変わったものがあるかを確かめる。
  • 識別子が https://<host>/tenant/ の発行者に対してウォレットを動かし、どのメタデータ URL を要求するかを記録する(1.0 Final と #775 の比較)。
  • 1.0 に従う AS と 1.1 に従う AS に、誤った tx_code で Pre-Authorized Code の Token Request を送り、invalid_grant と invalid_tx_code のどちらが返るかを記録する。
  • DPoP で束縛したクライアントで 1.1 Section 6.2.1.1 の Interactive Authorization の例をたどり、別の DPoP 鍵では同じ auth_session を使えないことを確かめる。

情報源

背景

きっかけ

subject.json にはこのリポジトリを選んだ理由がない(ai_note が空)。openid トラックで、まだディープダイブのない候補の先頭だった。本ディープダイブは、コミット 21ffd6c(2026 年 10 月 27 日)時点のドラフトと、引用したプルリクエストで内容を確認した。

なぜ重要か

OpenID4VCI 1.0 は OpenID Final Specification(2025 年 9 月 16 日承認)で、HAIP 1.0 がプロファイルする OpenID4VC 仕様群のうち発行を担う部分である。作業は 3 か所に分かれた。1.0 のエラッタ(メタデータ URL の規則と Transaction Code の推測対策)、1.1(OAuth 2.0 for First-Party Applications を土台にした Interactive Authorization、Credential Dataset のパラメーター、invalid_tx_code)、自前のバックエンドを持つウォレット提供者向けの新しい Server to Server ドラフトである。これらは通信内容が異なるので、実装者は相手がどの本文に従っているかを知る必要がある。

未解決の問題

  • 末尾の / の規則(#775)は 1.0 のエラッタとして公開されるのか。それはいつか。
  • 1.1 はいつ実装者向けドラフトになるのか(#687)。
  • オープンな s2s の変更(#810、#816)は、識別子の名前と遅延発行をどう決着させるのか。

仕様メモ

参考文献

21ffd6c 時点のドラフトが挙げる規範参照のうち、相互運用に特に効くもの:

  • API 全体の土台である OAuth 2.0(RFC 6749)、authorization_details のための Rich Authorization Requests(RFC 9396)、Pushed Authorization Requests(RFC 9126。1.1 では規範参照、1.0 では参考参照)、DPoP(RFC 9449)。
  • 特定の版に固定された、Final になっていない依存仕様(1.0 Section 14.7): OpenID Federation 1.0 draft -43、SD-JWT VC draft -11、Attestation-Based Client Authentication draft -07、Token Status List draft -12。1.1 はこれに OAuth 2.0 for First-Party Applications draft -04 を加える(1.1 Section 15.9)。
  • Interactive Authorization のプレゼンテーションの段階で使う OpenID4VP(1.1 Section 6.2.1.1)。
  • s2s: 全エンドポイントでの mutual TLS(RFC 8705)、エラー用の Problem Details(RFC 9457)、API の版の廃止を知らせる Sunset(RFC 8594)(s2s Sections 5.1、5.3、5.4)。

主な変更

各プルリクエストの差分で確認した。

  • 1.0 エラッタ -19: Section 12.2.2 に末尾の / を取り除く規則が戻った(#775)。Section 13.6.3「Transaction Code Guessing」が加わった(#686)。
  • 1.1 の Interactive Authorization(Section 6): Authorization Challenge Endpoint を使う First-Party Applications のプロファイルになった(#736)。401 ではなく 403 を返す(#781)。インタラクション種別は URN になった(#712)。
  • 1.1 の auth_session: インタラクション種別が求める場合だけ必須(#706)。ウォレットは進行中の認可リクエストの流れの中で最新の値を送る(#710)。DPoP を使う場合、AS はこれを DPoP 公開鍵と結び付けなければならない(MUST)(#803)。
  • 1.1 の auth_via_web: ウォレットは request_uri を一度だけ使い(#704)、並行セッションを持つときは state を送る(#797)。
  • 1.1 の Credential Response: credential_metadata(#721)、credential_dataset_id と credential_dataset_tag(#472)。Token Error Response: invalid_tx_code(#686)。
  • 1.0 と 1.1: 遅延発行の例が 202 Accepted になった(#722)。鍵バインディングが不要なら cryptographic_binding_methods_supported を省く(#714)。
  • s2s: 最初のドラフト(#753)、snake_case のプロパティ(#809)。オープン: 識別子の名前変更(#810)、遅延クレデンシャル(#816)。
  • 1.1 でオープン: issuer_redirect_uri と expected_redirect_uris(#759。1.0 側は grants の記述を直すだけ)。1.0 と 1.1 でオープン: proof の種類ごとの nonce の条件(#678)。1.1 でオープン: error_code の記述の修正(#819)。

影響分析

  • 上流: Interactive Authorization は First-Party Applications ドラフトに依存し、-04 に固定している。#819 が記述の修正で済むのは、そのエラー形式(error、Section 5.2.2)のためである。SD-JWT VC のフォーマットプロファイルは、固定された Final 前の SD-JWT VC の版に依存する。
  • 下流: HAIP 1.0 は OpenID4VCI をプロファイルしている(OpenID DCP WG の仕様一覧ページ)。1.0 Final で作ったウォレットと発行者は、エラッタのメタデータ規則の影響を受ける。1.1 も追う実装は、invalid_tx_code と Credential Dataset のパラメーターの影響を受ける。
  • 互換性: #775 は末尾が / の識別子のメタデータ URL を変え、#744 はこれを技術的には破壊的変更だとしている。#686 は Transaction Code の誤りに返すエラーを invalid_grant(1.0)から invalid_tx_code(1.1)に変えた。#819 は通信内容を変えない。s2s の名前変更は、安定版のないドラフトの中で JSON の名前を変える。

相互運用チェックリスト

節番号は、1.1 または s2s と書いたもの以外は 1.0 のもので、コミット 21ffd6c 時点である。

  • 各ピアがどの本文を実装しているかを確認する: 1.0 Final、1.0 のエラッタ版ドラフト、1.1、s2s。以下の項目でそれぞれ挙動が違う。
  • https://<host>/tenant と https://<host>/tenant/ からメタデータ URL を組み立てる。エラッタ版と 1.1 では、どちらも /.well-known/openid-credential-issuer/tenant になる。1.0 Final には末尾の / の規則がなく、文字どおり読めば後者は末尾に / が残る(Section 12.2.2、#744、#775)。
  • Credential Offer を値渡し(credential_offer)と参照渡し(credential_offer_uri)で送り、両方を含む Offer をウォレットが拒否することを確認する(Section 4.1)。
  • 誤った tx_code で Pre-Authorized Code の Token Request を送る。1.0 の AS は invalid_grant、1.1 の AS は invalid_tx_code を返すはずである(1.0 Section 6.3、1.1 Section 7.3)。
  • 誤った tx_code を繰り返し送り、AS がエラッタ版の SHOULD に従うかを確かめる。上限までは、コードが無効だと分かるエラーを返し、上限を超えたら Pre-Authorized Code を無効にして invalid_grant を返す(エラッタ版 Section 13.6.3)。いずれも SHOULD なので、不合格にはせず挙動を記録する。
  • 発行者が Nonce Endpoint を持つ場合、nonce のない jwt proof が invalid_proof で、古い c_nonce を使った proof が invalid_nonce で拒否されることを確認する(Section 8.3.1.2、Appendix F.1)。
  • cryptographic_binding_methods_supported のない設定で、ウォレットが proofs を省き、発行者がそのリクエストを受け付けることを確認する(Section 14.2、#714)。
  • 遅延レスポンスを返し、ウォレットが 202、transaction_id、interval に従い、Deferred Credential Endpoint からクレデンシャルを受け取ることを確認する(Sections 8.3 と 9)。
  • 1.1 の Interactive Authorization: insufficient_authorization が 403 Forbidden で返り、エラーのキーが例と同じく error であって error_code ではないことを確認する(Section 6.2.1、#781、#819)。
  • 1.1: ウォレットが常に最新の auth_session を送ること、DPoP を使う場合は別の DPoP 鍵で送られたその auth_session を AS が拒否することを確認する(Section 6.2.1、#710、#803)。
  • 1.1 の auth_via_web: ウォレットが request_uri を一度だけ使い、並行セッションを持つときに state を送ること、AS が期限切れの request_uri を拒否することを確認する(Section 6.2.1.2、#704、#797)。
  • 1.1: プレゼンテーション要求に expected_origins がある場合、それが Authorization Challenge Endpoint から導いた Origin だけを含み、ウォレットがそれを検証することを確認する(Section 6.2.1.1)。
  • 1.1: credential_dataset_id があるときは credential_dataset_tag もあること、タグが変わったらウォレットが保存済みのクレデンシャルを置き換えることを確認する(Sections 9.3 と 15.4)。
  • s2s: 両方のサーバーが mTLS を使い、Api-Version を送り、未対応の版には HTTP 400 と Problem Details で応答することを確認する(s2s Sections 5.1 と 5.3)。

検証ログ

環境

  • ランタイム:
  • ツール:
  • バージョン:

手順

結果

失敗パターン

次にやること