メインコンテンツまでスキップ

Verifier機能のセットアップと使用方法

このガイドでは、VCKnotsのVerifier機能のセットアップと使用方法について説明します。

1. 前提条件

  • OpenID for Verifiable Presentations - draft 24 に対応(OpenID for Verifiable Presentations - draft 24)   以下は現時点では未実装ですが、今後対応予定です。
    • response_modedirect_postは対応していますが、direct_post.jwtは未対応です(現時点では未実装/今後対応予定)。
    • presentation_definition_uriに未対応(今後対応予定)
    • vp_token は単一の String 型のみ対応(JSON VP 形式は未対応/今後対応予定)
  • クロスデバイスフローを前提としています
  • Node.js v14以降がインストールされていること
  • TypeScriptが設定されていること
  • 本ドキュメントはserverのサンプル実装に基づいて説明します
  • HonoのWebフレームワークを使用していますが、他のフレームワークでも利用可能です
  • 現在対応しているclient_id_schema:x509_san_dns、redirect_uriになります
  • 現在対応しているVPフォーマットについては、VPはjwt_vp_json、VCはjwt_vc_jsonに対応しています。また、dc+sd-jwtにも対応しています。
  • stateパラメータについては、実装者の責任での実装となります

2. 初期設定

必要な依存関係のインストール

npm install @trustknots/vcknots
npm install hono @hono/node-server

ライブラリを使うための準備

import { Hono } from 'hono'
import { initializeContext } from '@trustknots/vcknots'
import { initializeVerifierFlow, VerifierMetadata, VerifierClientId, VerifierAuthorizationResponse } from '@trustknots/vcknots/verifier'

const app = new Hono();

// VcknotsContextを作成
const context = initializeContext({
debug: process.env.NODE_ENV !== "production",
});

// VerifierFlowインスタンスを作成
const verifierFlow = initializeVerifierFlow(context);

3. Verifier機能のサンプル実装

はじめに:

1. Authorizationリクエストの作成

Verifier が Wallet に提示を依頼するための認可リクエスト(openid4vp://authorize?...)を生成します。

1-1. 基本的な認可リクエスト

このエンドポイントは OAuth 2.0 に準拠した認可リクエスト形式を使用します。

  • エンドポイント: POST /verify/request

  • リクエストボディ (JSON)

    • credentialId (string, 必須): 要求する VC の type を指定。例: UniversityDegreeCredential。未指定の場合はエラー。
  • レスポンス

    • 200 OK: テキストで openid4vp://authorize?... 形式の認可リクエスト URL を返却。
    • 400 Bad Request: credentialId 未指定時など。
  • 実際のコード

app.post('/verify/request', async (c) => {
try {
const verifierId = VerifierClientId(baseUrl)
const { credentialId } = (await c.req.json()) ?? {};

if (!credentialId) throw err("INVALID_REQUEST");
const client_id = 'x509_san_dns:localhost'

const query = {
presentation_definition: {
id: randomUUID(),
name: 'Test Name',
purpose: 'Test Purpose',
input_descriptors: [
{
id: credentialId,
format: {
jwt_vc_json: {
proof_type: ['ES256'],
},
},
constraints: {
fields: [
{
path: ['$.vc.type'],
filter: {
type: 'array',
contains: {
const: 'VerifiableCredential',
},
},
},
],
},
},
],
},
}

const request = await verifierFlow.createAuthzRequest(
verifierId,
'vp_token',
client_id,
'direct_post',
query,
false,
{
response_uri: `${baseUrl}/verifiers/${encodeURIComponent(verifierId)}/callback`,
base_url: baseUrl,
}
)

const encoded = Object.entries(request)
.map(([key, value]) => {
const encode = value && typeof value === 'object' ? JSON.stringify(value) : String(value)
return `${encodeURIComponent(key)}=${encodeURIComponent(encode)}`
})
.join('&')

return c.text(`openid4vp://authorize?${encoded}`)
} catch (error) {
return c.json({ error: 'internal_server_error' }, 400)
}
})

リクエスト

curl --location 'http://localhost:8080/verify/request' \
--header 'Content-Type: application/json' \
--data ' {
"credentialId": "UniversityDegreeCredential"
}'

レスポンス

openid4vp://authorize?response_type=vp_token&client_id=x509_san_dns%3Alocalhost&client_metadata=%7B%22client_name%22%3A%22Sample%20Verifier%20App%22%2C%22client_uri%22%3A%22http%3A%2F%2Flocalhost%3A8080%22%2C%22jwks%22%3A%7B%22keys%22%3A%5B%7B%22kty%22%3A%22EC%22%2C%22x%22%3A%220_3S7HedSywaxlekdt6Or8pkcR13hQaCPMqt9cuZBVc%22%2C%22y%22%3A%22ZVXSCL3HlnMQWKrwMyIAe5wsAIWd3Eu1misKFr3POdA%22%2C%22crv%22%3A%22P-256%22%7D%5D%7D%2C%22vp_formats%22%3A%7B%22jwt_vp_json%22%3A%7B%22alg%22%3A%5B%22ES256%22%5D%7D%7D%2C%22client_id_scheme%22%3A%22redirect_uri%22%2C%22authorization_signed_response_alg%22%3A%22ES256%22%7D&nonce=5cf220cd62d3453192b1af4f6ba88b87&response_mode=direct_post&response_uri=http%3A%2F%2Flocalhost%3A8080%2Fverifiers%2Fhttp%253A%252F%252Flocalhost%253A8080%2Fcallback&client_id_scheme=x509_san_dns&presentation_definition=%7B%22id%22%3A%2243bff439-6929-4843-931f-5b7530ed8010%22%2C%22name%22%3A%22Test%20Name%22%2C%22purpose%22%3A%22Test%20Purpose%22%2C%22input_descriptors%22%3A%5B%7B%22id%22%3A%22UniversityDegreeCredential%22%2C%22format%22%3A%7B%22jwt_vc_json%22%3A%7B%22proof_type%22%3A%5B%22ES256%22%5D%7D%7D%2C%22constraints%22%3A%7B%22fields%22%3A%5B%7B%22path%22%3A%5B%22%24.vc.type%22%5D%2C%22filter%22%3A%7B%22type%22%3A%22array%22%2C%22contains%22%3A%7B%22const%22%3A%22VerifiableCredential%22%7D%7D%7D%5D%7D%7D%5D%7D

1-2. JAR(JWT Authorization Request)形式のリクエスト

このエンドポイントは JWT Authorization Request (JAR) を用いて Request Object を生成・保存し、Wallet が取得するための認可リクエスト URI を返します。

  • エンドポイント: POST /verify/request-object

  • リクエストボディ (JSON)

    • 以下のフィールドを含めます。
      • query.presentation_definition
      • state
      • response_uri
      • client_idredirect_uri:<URL> または x509_san_dns:<ホスト名> を指定
  • レスポンス

    • 200 OK: テキストで openid4vp://authorize?... 形式の認可リクエスト URL を返却(request_uri 情報を含みます)。
    • 400 Bad Request: JSON が不正な場合など、リクエスト内容に問題があるとき。
  • 実際のコード

  verifyApp.post('/verify/request-object', async (c) => {
try {
const verifierId = VerifierClientId(baseUrl)

const body = await c.req.json()
if (!body) throw err('INVALID_REQUEST')
const {
client_id: clientId,
state,
response_uri: responseUri,
query: presentationDefinition,
} = body
const request = await verifierFlow.createAuthzRequest(
verifierId,
'vp_token',
clientId,
'direct_post',
presentationDefinition,
true,
{
state: state,
base_url: baseUrl,
response_uri: responseUri,
}
)
const encoded = Object.entries(request)
.map(([key, value]) => {
const encode = value && typeof value === 'object' ? JSON.stringify(value) : String(value)
return `${encodeURIComponent(key)}=${encodeURIComponent(encode)}`
})
.join('&')

return c.text(`openid4vp://authorize?${encoded}`)
} catch (err) {
return c.json(handleError(err), 400)
}
})

リクエスト

curl --location 'http://localhost:8080/verify/request-object' \
--header 'Content-Type: application/json' \
--data '{
"query": {
"presentation_definition": {
"id": "example",
"name": "",
"purpose": "",
"submission_requirements": [],
"input_descriptors": [
{
"id": "University Degree Credentials",
"name": "Example",
"purpose": "to verify your UniversityDegree Credential",
"format": {
"dc+sd-jwt": {
"sd-jwt_alg_values": [
"ES256"
],
"kb-jwt_alg_values": [
"ES256"
]
}
},
"constraints": {
"fields": [
{
"path": [
"$.vct"
],
"filter": {
"type": "string",
"const": "urn:eudi:pid:1"
}
},
{
"path": [
"$.family_name"
],
"intent_to_retain": false
},
{
"path": [
"$.given_name"
],
"intent_to_retain": false
},
{
"path": [
"$.age_equal_or_over.18"
],
"intent_to_retain": false
}
]
}
}
]
}
},
"state": "example-state",
"client_id": "x509_san_dns:localhost",
"is_transaction_data":false,
"response_uri":"http://localhost:8080/callback-kbjwt"}'

レスポンス

openid4vp://authorize?client_id=x509_san_dns%3Alocalhost&request_uri=http%3A%2F%2Flocalhost%3A8080%2Fverifiers%2Fhttp%253A%252F%252Flocalhost%253A8080%2Frequest.jwt%2F0aab8b5062b0410ba96f1afaf3925f93

2. リクエストオブジェクトの取得

JAR 生成時に保存された Request Object(JWT)を Wallet などのクライアントが取得するためのエンドポイントです。

  • エンドポイント: GET /verify/request.jwt/:request-object-Id

  • パスパラメーター

    • request-object-Id: createAuthzRequest のレスポンスに含まれる request_uri(末尾の ID)で指定します。
  • レスポンス

    • 200 OK: Content-Type: application/oauth-authz-req+jwt の JWT 本文を返却。
    • 400 Bad Request: ID が不正な場合や内部エラーが発生した場合。
  • 実際のコード

verifyApp.get('/verify/request.jwt/:request-object-Id', async (c) => {
try {
const verifierId = VerifierClientId(baseUrl)
const requestObjectId = RequestObjectId(c.req.param('request-object-Id'))
const jar = await verifierFlow.findRequestObject(verifierId, requestObjectId)
return c.body(jar, 200, {
'Content-Type': 'application/oauth-authz-req+jwt',
})
} catch (err) {
return c.json(handleError(err), 400)
}
})

リクエスト

curl --location 'http://localhost:8080/verify/request.jwt/fca442d1b80a43c7bb3faeb13e9a3b73'

レスポンス

eyJhbGciOiJFUzI1NiIsInR5cCI6Im9hdXRoLWF1dGh6LXJlcStqd3QiLCJ4NWMiOlsiXG5NSUlDSGpDQ0FjT2dBd0lCQWdJVVpYOUJTNUNET0pSVzJ0MUZLMVVETXQvUXdNRXdDZ1lJS29aSXpqMEVBd0l3XG5JVEVMTUFrR0ExVUVCaE1DUjBJeEVqQVFCZ05WQkFNTUNVOUpSRVlnVkdWemREQWVGdzB5TkRFeE1qVXdPRE0yXG5NRFJhRncwek5ERXhNak13T0RNMk1EUmFNQ0V4Q3pBSkJnTlZCQVlUQWtkQ01SSXdFQVlEVlFRRERBbFBTVVJHXG5JRlJsYzNRd1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFUVC9kTHNkNTFMTEJyR1Y2UjIzbzZ2XG55bVJ4SFhlRkJvSTh5cTMxeTVrRlYyVlYwZ2k5eDVaekVGaXE4RE1pQUh1Y0xBQ0ZuZHhMdFpvckNoYTl6em5RXG5vNEhZTUlIVk1CMEdBMVVkRGdRV0JCUzVjYmRnQWVNQmk1d3hwYnB3SVNHaFNoQVdFVEFmQmdOVkhTTUVHREFXXG5nQlM1Y2JkZ0FlTUJpNXd4cGJwd0lTR2hTaEFXRVRBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUlHQkJnTlZIUkVFXG5lakI0Z2hCM2QzY3VhR1ZsYm1GdUxtMWxMblZyZ2gxa1pXMXZMbU5sY25ScFptbGpZWFJwYjI0dWIzQmxibWxrXG5MbTVsZElJSmJHOWpZV3hvYjNOMGdoWnNiMk5oYkdodmMzUXVaVzF2WW1sNExtTnZMblZyZ2lKa1pXMXZMbkJwXG5aQzFwYzNOMVpYSXVZblZ1WkdWelpISjFZMnRsY21WcExtUmxNQW9HQ0NxR1NNNDlCQU1DQTBrQU1FWUNJUUNQXG5ibkx4Q0krV1IxdmhPVytBOEt6bkFXdjFNSm8rWUViMU1JNDVOS1cvVlFJaEFMenNxb3g4VnVCUndOMmRsNUxrXG5wbnhQNG9IOXA2SDBBT1ptS1ArWTduWFNcbiJdfQ.eyJyZXNwb25zZV90eXBlIjoidnBfdG9rZW4iLCJjbGllbnRfaWQiOiJ4NTA5X3Nhbl9kbnM6bG9jYWxob3N0Iiwic3RhdGUiOiIwMzg0NzViMDEyNmI0Njg0YTIyNmJjODBlYWM5MzRiNiIsImNsaWVudF9tZXRhZGF0YSI6eyJjbGllbnRfbmFtZSI6IlNhbXBsZSBWZXJpZmllciBBcHAiLCJjbGllbnRfdXJpIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwIiwiandrcyI6eyJrZXlzIjpbeyJrdHkiOiJFQyIsIngiOiIwXzNTN0hlZFN5d2F4bGVrZHQ2T3I4cGtjUjEzaFFhQ1BNcXQ5Y3VaQlZjIiwieSI6IlpWWFNDTDNIbG5NUVdLcndNeUlBZTV3c0FJV2QzRXUxbWlzS0ZyM1BPZEEiLCJjcnYiOiJQLTI1NiJ9XX0sInZwX2Zvcm1hdHMiOnsiand0X3ZwIjp7ImFsZyI6WyJFUzI1NiJdfX0sImNsaWVudF9pZF9zY2hlbWUiOiJyZWRpcmVjdF91cmkiLCJhdXRob3JpemF0aW9uX3NpZ25lZF9yZXNwb25zZV9hbGciOiJFUzI1NiJ9LCJyZXNwb25zZV9tb2RlIjoiZGlyZWN0X3Bvc3QiLCJyZXNwb25zZV91cmkiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvdmVyaWZpZXJzL2h0dHAlM0ElMkYlMkZsb2NhbGhvc3QlM0E4MDgwL2NhbGxiYWNrIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwIiwiYXVkIjoiaHR0cHM6Ly9zZWxmLWlzc3VlZC5tZS92MiIsInByZXNlbnRhdGlvbl9kZWZpbml0aW9uIjp7ImlkIjoiODkyMGVjMGUtZDc3YS00MmJlLTk4OWQtZTU1MTBjZmFhNjlkIiwibmFtZSI6IlRlc3QgTmFtZSIsInB1cnBvc2UiOiJUZXN0IFB1cnBvc2UiLCJpbnB1dF9kZXNjcmlwdG9ycyI6W3siaWQiOiI4ZjJmZWM3ZC1hMmI5LTRhZTEtYTdmMi1mMGJmMTgyMWYzY2UiLCJmb3JtYXQiOnsiand0X3ZjX2pzb24iOnsicHJvb2ZfdHlwZSI6WyJFUzI1NiJdfX0sImNvbnN0cmFpbnRzIjp7ImZpZWxkcyI6W3sicGF0aCI6WyIkLnZjLnR5cGUiXSwiZmlsdGVyIjp7InR5cGUiOiJhcnJheSIsImNvbnRhaW5zIjp7ImNvbnN0IjoiVmVyaWZpYWJsZUNyZWRlbnRpYWwifX19XX19XX0sImlhdCI6MTc2MTkwMTAzOCwibm9uY2UiOiI0YTVhYTQ1ZjllMWQ0N2FmOTkzNWY5OWEyM2M5ZDNlNiJ9.Kc4FFI1cNXJCO5nI8Yy0jnlYtLFDL-Wr-AoWtq8sasI0grzP1Zco8Zw9Ug2zybtMnn_o6XLDnnRj8jb2g0Y0TQ

3. vp_token の受信と検証

Wallet から返送される vp_token を受け取り、Verifier 側で検証 (VP 検証) を行うエンドポイントです。

  • エンドポイント: POST /verify/callback

  • リクエストボディ

    • Content-Type: application/x-www-form-urlencoded
    • フォームフィールドに vp_tokenpresentation_submission など、OpenID4VP の Authorization Response パラメータを載せます(Wallet の direct_post 応答と同形式)。
    • 値は検証のうえ VerifierAuthorizationResponse にパースされます。
  • レスポンス

    • 200 OK: JSON 本文 { "redirect_uri": "<baseUrl>/verified" }(サンプルサーバの挙動。アプリに合わせて変更してください)。
    • 400 Bad Request / 500 Internal Server Error: バリデーションまたは VP 検証失敗時に handleError 由来の OAuth 形式 JSON(error, error_description)。
  • 関連エンドポイント: POST /verify/callback-kbjwt — 認可リクエストが x509_san_dns かつ SD-JWT + Key Binding JWT を使うサンプル向け。verifyPresentationsisKbJwt: true と、ベース URL のホストから組み立てた expectedAudx509_san_dns:…)で呼び出し、認可リクエストの client_id と KB-JWT の aud を一致させます。

  • コード例

verifyApp.post('/verify/callback', async (c) => {
try {
const verifierId = VerifierClientId(baseUrl)
const parsed = parseFormPayload(await c.req.formData())
if (!parsed.ok) {
return c.json(parsed.error, 400)
}

const authorizationResponse = VerifierAuthorizationResponse(parsed.payload)

const vpPayload = await verifierFlow.verifyPresentations(verifierId, authorizationResponse, {
expectedAud: ClientIdentifier(`redirect_uri:${baseUrl}/callback`),
})

return c.json({ redirect_uri: `${baseUrl}/verified` }, 200)
} catch (err) {
const errorResponse = handleError(err)
const status = errorResponse.error === 'internal_server_error' ? 500 : 400
return c.json(errorResponse, status)
}
})

リクエスト

curl --location 'http://localhost:8080/verify/callback' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'vp_token=eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImRpZDprZXk6ekRuYWVZaXdITmVNWWFqMjFXbzlqUENvd3RuQnJZOGhlOFVDSzhaWk4xbWhoeDhQTSJ9.eyJpc3MiOiJkaWQ6a2V5OnpEbmFlWWl3SE5lTVlhajIxV285alBDb3d0bkJyWThoZThVQ0s4WlpOMW1oaHg4UE0iLCJub25jZSI6Ijg5NDAwOWRkOGJmMDQxMzBhMWVlYmFkZmM1YTE3MmRkIiwiYXVkIjoicmVkaXJlY3RfdXJpOmh0dHBzOi8vOXBuenBiYmctODA4MC5hc3NlLmRldnR1bm5lbHMubXMvY2FsbGJhY2siLCJ2cCI6eyJ0eXBlIjpbIlZlcmlmaWFibGVQcmVzZW50YXRpb24iXSwidmVyaWZpYWJsZUNyZWRlbnRpYWwiOlsiZXlKaGJHY2lPaUpGVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5LmV5SjJZeUk2ZXlKQVkyOXVkR1Y0ZENJNld5Sm9kSFJ3Y3pvdkwzZDNkeTUzTXk1dmNtY3ZNakF4T0M5amNtVmtaVzUwYVdGc2N5OTJNU0pkTENKcFpDSTZJbWgwZEhCek9pOHZPWEJ1ZW5CaVltY3RPREE0TUM1aGMzTmxMbVJsZG5SMWJtNWxiSE11YlhNdmRtTXZPVEV4T0dOa09UVTNaREF6TkRneFlqazNZakF4WmpNME5qTTVPRFk0TjJZaUxDSjBlWEJsSWpwYklsWmxjbWxtYVdGaWJHVkRjbVZrWlc1MGFXRnNJaXdpVlc1cGRtVnljMmwwZVVSbFozSmxaVU55WldSbGJuUnBZV3dpWFN3aWFYTnpkV1Z5SWpvaWFIUjBjSE02THk4NWNHNTZjR0ppWnkwNE1EZ3dMbUZ6YzJVdVpHVjJkSFZ1Ym1Wc2N5NXRjeUlzSW1semMzVmhibU5sUkdGMFpTSTZJakl3TWpZdE1ETXRNalpVTVRBNk1USTZNVFF1TVRZNVdpSXNJbU55WldSbGJuUnBZV3hUZFdKcVpXTjBJanA3SW1sa0lqb2laR2xrT210bGVUcDZSRzVoWlZscGQwaE9aVTFaWVdveU1WZHZPV3BRUTI5M2RHNUNjbGs0YUdVNFZVTkxPRnBhVGpGdGFHaDRPRkJOSWl3aVoybDJaVzVmYm1GdFpTSTZJblJsYzNRaUxDSm1ZVzFwYkhsZmJtRnRaU0k2SW5SaGNtOGlMQ0prWldkeVpXVWlPaUkxSWl3aVozQmhJam9pZEdWemRDSjlmU3dpYVhOeklqb2lhSFIwY0hNNkx5ODVjRzU2Y0dKaVp5MDRNRGd3TG1GemMyVXVaR1YyZEhWdWJtVnNjeTV0Y3lJc0luTjFZaUk2SW1ScFpEcHJaWGs2ZWtSdVlXVlphWGRJVG1WTldXRnFNakZYYnpscVVFTnZkM1J1UW5KWk9HaGxPRlZEU3poYVdrNHhiV2hvZURoUVRTSjkucUU5aVlxYngzVHNWNjhGQVR2Rl84b083T0VsallPeWR2eGJOQnlyazJpY1Q2c0l2WE5QS0NiYTlwcUxvOWVPNjNEdy1PdDNVZHBPMm10Q3lXOGM1a1EiXSwiaG9sZGVyIjoiZGlkOmtleTp6RG5hZVlpd0hOZU1ZYWoyMVdvOWpQQ293dG5Cclk4aGU4VUNLOFpaTjFtaGh4OFBNIn19.hzXVRIennFIu1CiYXCQB_W-w4xL-Un9PChp5c31ZPI2CbmZaCJsjkuvwEm6c6_5F1nY7UkAF-EsVqewbsdne6Q' \
--data-urlencode 'presentation_submission={
"id": "BptkWumGAD21zS6uRm2a",
"definition_id": "3cf37e60-e6e4-4d67-acff-3623586a7c4c",
"descriptor_map": [
{
"id": "BptkWumGAD21zS6uRm2a",
"format": "jwt_vp_json",
"path": "$",
"path_nested": {
"id": "BptkWumGAD21zS6uRm2a",
"format": "jwt_vc_json",
"path": "$.verifiableCredential[0]"
}
}
]
}' \
--data-urlencode 'state=example-state'

レスポンス200 OK

{
"redirect_uri": "http://localhost:8080/verified"
}

4. Verifierメタデータの登録

  • 本ガイドのコードは、起動時に本セクションの手順に従ってVerifierメタデータを登録します。実運用や各自の開発環境に合わせて、BASE_URLおよびメタデータ/証明書ファイルを適宜調整してください。

メタデータファイル(外部JSON):

  • 場所: vcknots/server/samples/verifier_metadata.json
  • 例(内容):
{
"vp_formats": {
"jwt_vc_json": {
"alg_values_supported": ["ES256"]
},
"jwt_vp_json": {
"alg_values_supported": ["ES256"]
},
"dc+sd-jwt": {
"sd-jwt_alg_values": ["ES256", "ES384"],
"kb-jwt_alg_values": ["ES256", "ES384"]
}
}
}

証明書ファイルの場所:

  • 秘密鍵: vcknots/server/samples/certificate-openid-test/private_key_openid.pem
  • 証明書: vcknots/server/samples/certificate-openid-test/certificate_openid.pem
// BASE_URL を反映してメタデータを初期化
const baseUrl = process.env.BASE_URL ?? 'http://localhost:8080'

// サンプルの verifier メタデータ(JSON) を読み込んだものを利用(例: verifierMetadataConfig)
verifierMetadataConfig.client_uri = baseUrl
await initializeVerifierMetadata(baseUrl, verifierMetadataConfig)
// 証明書/秘密鍵を読み込み、メタデータを登録
async function initializeVerifierMetadata(verifierId: string, metadata: VerifierMetadata) {
try {
const clientId = VerifierClientId(verifierId)

const __dirname = dirname(fileURLToPath(import.meta.url))
const privateKeyPath = join(
__dirname,
'..',
'samples/certificate-openid-test/private_key_openid.pem'
)
const certificatePath = join(
__dirname,
'..',
'samples/certificate-openid-test/certificate_openid.pem'
)
const option = {
privateKey: readFileSync(privateKeyPath, 'utf-8'),
certificate: readFileSync(certificatePath, 'utf-8'),
format: 'pem',
alg: 'ES256',
} as const

await verifierFlow.createVerifierMetadata(clientId, metadata, option)
console.log(`Verifier metadata initialized for ${clientId}`)
return true
} catch (error) {
console.error('Error initializing verifier metadata:', error)
return false
}
}

6. 型定義の説明

VerifierClientId

Verifierの識別子を表す型です。ClientIdSchemeと識別子を組み合わせた形式で、Verifierの一意な識別に使用されます。

定義は issuer+verifier/src/client-id.types.ts を参照してください。

VerifierMetadata

Verifierのメタデータを定義する型です。クライアント名、URI、サポートするVP形式、リダイレクトURIなどの情報を含みます。

定義は issuer+verifier/src/verifier-metadata.types.ts を参照してください。

VerifierAuthorizationResponse

VP Tokenやプレゼンテーション提出情報を含み、プレゼンテーションの検証に使用されます。

定義は issuer+verifier/src/authorization-response.types.ts を参照してください。

VpTokenPayload

verifyPresentations が返す検証済みペイロードを表す型です。 VP フォーマット(例: jwt_vp_json / dc+sd-jwt)に応じたユニオン型です。

定義は issuer+verifier/src/presentation.types.ts を参照してください。

7. VerifierFlowの各メソッド

createVerifierMetadata

Verifierのメタデータを作成・保存します。

createVerifierMetadata(
verifierId: VerifierClientId,
metadata: VerifierMetadata,
options?: CreateVerifierMetadataOptions
): Promise<void>

パラメータ:

戻り値:

  • なし

エラーケース:

  • DUPLICATE_VERIFIER: 既に同じverifierIdのメタデータが登録済み
  • INTERNAL_SERVER_ERROR: options.algが未指定(公開鍵/証明書を指定する場合は必須)
  • INVALID_CERTIFICATE: 提供された証明書が無効

CreateVerifierMetadataOptions

Verifierメタデータ作成時のオプションを定義する型です。証明書または公開鍵の設定が可能です。

詳細な型定義については、verifier.flows.tsを参照してください。

createAuthzRequest

認可リクエストを作成します。

createAuthzRequest(
verifierId: ClientId,
response_type: 'vp_token',
client_id: `${ClientIdScheme}:${string}`,
response_mode: 'direct_post' | 'query' | 'fragment' | 'dc_api.jwt' | 'dc_api',
query: DeepPartialUnknown<PresentationExchange> | DeepPartialUnknown<Dcql>,
isRequestUri: boolean,
options: CreateAuthzRequestOptions
): Promise<AuthorizationRequest>

パラメータ:

戻り値:

  • AuthorizationRequestオブジェクトを返します。(AuthorizationRequest)このオブジェクトは以下の形式のいずれかになります:

    • request_uri形式 (isRequestUri = trueの場合):
    {
    client_id: string,
    request_uri: string
    }
    • 直接形式 (isRequestUri = falseの場合):
    {
    client_id: string,
    response_uri: string,
    response_type: 'vp_token',
    response_mode: 'direct_post' | 'query' | 'fragment' | 'dc_api.jwt' | 'dc_api',
    client_id_scheme: string,
    client_metadata: VerifierMetadata,
    nonce: string,
    // presentaion_defition または dcql_query
    }

エラーケース:

  • UNSUPPORTED_CLIENT_ID_SCHEME: 未対応のclient_id_schemeが指定された
  • CERTIFICATE_NOT_FOUND: x509_san_dnsまたはx509_san_uri利用時に証明書未登録
  • INVALID_REQUEST: isRequestUri = trueなのにoptions.base_urlが未指定

CreateAuthzRequestOptions

認証リクエスト作成時のオプションを定義する型です。

詳細な型定義については、verifier.flows.tsを参照してください。

注意事項:

  • isRequestUritrueの場合、base_urlは必須です
  • response_uriが指定されない場合、デフォルトで${verifierId}/postが使用されます
  • stateはセキュリティのため、ランダムで予測困難な値を使用することを推奨します

AuthorizationRequest(createAuthzRequest のレスポンス型)

createAuthzRequest が返すレスポンス型です。request_uri を用いる「Request URI 形式」か、パラメータを直接含める「直接形式」のいずれかで、PE(Presentation Exchange)または DCQL のスキーマと結合されます。

詳細な型定義については、authorization-request.types.tsを参照してください。

findRequestObject

createAuthzRequestでJAR形式のレスポンスの場合に、JAR形式のリクエストオブジェクトを取得します。

findRequestObject(
verifierId: ClientId,
objectId: RequestObjectId,
options?: FindRequestObjectOptions
): Promise<string>

パラメータ:

戻り値:

  • JWT形式のRequest Object文字列を返します。この文字列は以下の形式になります:
{base64url(header)}.{base64url(payload)}.{signature}

エラーケース:

  • VERIFIER_NOT_FOUND: 指定したVerifierが存在しない
  • REQUEST_OBJECT_NOT_FOUND: 指定したRequest Objectが存在しない
  • PROVIDER_NOT_FOUND: Authorization Request JARのプロバイダが見つからない
  • AUTHZ_VERIFIER_KEY_NOT_FOUND: 指定アルゴリズムの署名鍵プロバイダが見つからない
  • INTERNAL_SERVER_ERROR: Request Objectの署名生成に失敗

注意事項:

  • リクエストオブジェクトは取得は一度のみとなります。
  • 同じRequest Object IDで複数回呼び出すとエラーになります。

RequestObjectId

Request Object(認可リクエストJAR)の一意識別子です。

詳細な型定義については、request-object-id.types.tsを参照してください。

FindRequestObjectOptions

リクエストオブジェクト取得時のオプションを定義する型です。

詳細な型定義については、verifier.flows.tsを参照してください。

verifyPresentations

VP Tokenを検証します。

verifyPresentations(
id: ClientId,
response: AuthorizationResponse,
options: VerifyPresentationOptions
): Promise<VpTokenPayload>

パラメータ:

VerifyPresentationOptions

Verifier アプリから VP/クレデンシャル形式ごとの検査に渡すオプションです。型定義は verifier.flows.ts を参照してください。

フィールド必須説明
expectedAudはい認可リクエストで送った OAuth / OpenID4VP の client_id と同一である ClientIdentifier 文字列。jwt_vp_json の VP JWT の aud、および dc+sd-jwt で Key Binding がある場合の KB-JWT の aud と突き合わせられます。
specifiedDisclosuresいいえdc+sd-jwt 用。選択的開示の指定(任意)。
isKbJwtいいえdc+sd-jwt 用。true のとき Key Binding JWT を検証(nonceaudsd_hash など)。省略時はプロバイダ既定(未指定は KB-JWT 検証なしに寄せる動き)。
expectedNonceいいえdc+sd-jwt + KB-JWT 時。KB-JWT の nonce 期待値(通常は認可リクエストの nonce)。
expectedTransactionDataHashesいいえtransaction_data 利用時。KB-JWT に含まれるハッシュ列の期待値。

戻り値:

  • VpTokenPayload の検証済み VP トークンペイロードを返します。

  • 具体的には、サポートする VP フォーマット(例: jwt_vp_json / dc+sd-jwt)に対応したユニオン型のペイロードです。

  • いずれの場合も、標準的な JWT クレーム(例: iss, sub, aud, exp, iat)を含むことがあります。

    • 例(jwt_vp_json):
    {
    iss?: string,
    vp: {
    type: string[],
    verifiableCredential: (string | object)[]
    },
    nonce: string
    }
    • 例(dc+sd-jwt):
    {
    iss?: string,
    vct: string
    // _sd、cnf、status などの SD-JWT ペイロードクレームを含む場合があります
    }
  • 実装者側の扱い: 本ライブラリは検証済みペイロードを自動では保存・管理しません。セッションやデータベースへの紐づけ、保管期間、監査ログの有無などは、利用者(実装者)のアプリケーションが、業務要件に従って設計・実装してください。

エラーケース:

  • VERIFIER_NOT_FOUND: Verifierが存在しない
  • UNSUPPORTED_VP_TOKEN: vp_token の形や形式が未対応(複数 VP、オブジェクト形式の vp_token など)
  • ILLEGAL_ARGUMENT: 引数不備(例: presentation_submission なしの経路は未実装、プロバイダがオプションを拒否)
  • INVALID_NONCE: 認可リクエストの nonce が VP に無い、または一致しない
  • INVALID_CREDENTIAL: 内包 VC が無効(jwt_vp_json 経路)、発行者メタデータ/JWKS 取得失敗など
  • INVALID_VP_TOKEN: VP 構造または audexpectedAud と一致しないなど
  • INVALID_SD_JWT / HOLDER_BINDING_FAILED: SD-JWT または Key Binding の検証失敗
  • INVALID_PRESENTATION_SUBMISSION: 無効な presentation_submission

注意事項:

  • 認可リクエストで使った client_id と同じ文字列redirect_uri:x509_san_dns: などスキーム接頭辞込み)を expectedAud に渡してください。Wallet が設定する aud と一致させる必要があります。

findVerifierCertificate

Verifierの証明書を取得します。

findVerifierCertificate(id: ClientId): Promise<Certificate | null>

パラメータ:

戻り値:

  • 証明書オブジェクト(Certificate)、または存在しない場合はnull

Certificate

Verifierが保持する証明書チェーンを表す型です(PEM形式の文字列配列)。各要素はPEMフォーマット検証を通過したものに限られます。

詳細な型定義については、signature-key.types.tsを参照してください。

注意:

  • チェーン順は「リーフ → 中間 → ルート」を推奨
  • 無効なPEMはエラーとなります

8. 注意事項

  1. 証明書の管理: Verifierのメタデータを設定する際は、適切な証明書と秘密鍵を提供する必要があります。

    • 証明書チェーンの順序は重要です(リーフ証明書 → 中間証明書 → ルート証明書)
    • 本番環境では有効な証明書を使用してください
  2. セキュリティ: 本番環境では、適切な認証・認可の仕組みを実装してください。

    • 秘密鍵の管理には特に注意を払ってください
    • HTTPSを使用して通信を暗号化してください
  3. URLエンコード: verifier IDにURLエンコードが必要な文字(例::/)が含まれる場合は、適切にエンコードしてください。

9. トラブルシューティング

  • Q:証明書の関連のエラー:INVALID_CERTIFICATE

    • A: 証明書ファイルのパスが正しいか、ファイルが存在するかを確認してください。また、有効な証明書であることを確認してください。
  • Q:メタデータのバリデーションエラー:

    • A: 提供されたメタデータがVerifierMetadataスキーマに適合しているかを確認してください。
  • Q:認可リクエストの作成エラー:invalid_request

    • A: 必要なパラメータがすべて提供されているかを確認してください。
  • Q:リクエストオブジェクト取得エラー:REQUEST_OBJECT_NOT_FOUND

    • A: リクエストオブジェクトの取得は一度のみとなります。同じRequest Object IDで複数回呼び出すとエラーになります。
  • Q:vp_tokenのnonce検証エラー: INVALID_NONCE - nonce is not valid で失敗する。

    • A: 以下の原因と解決方法を確認してください。
    • 原因:
      • vp_token内のnonceが認可リクエスト時に生成されたものと一致しない
      • nonceが既に使用済み
      • nonceの有効期限が切れている
    • 解決方法:
      • 認可リクエスト時に生成されたnonceとvp_token内のnonceが一致することを確認
      • 同じnonceで複数回の認証を試行していないか確認
      • nonceの生成と保存処理が正しく動作しているか確認
      • 時計の同期が取れているか確認(有効期限チェックのため)