From a0e32a3bfe90083d7cebfbd7afcb5981fa39ddf0 Mon Sep 17 00:00:00 2001 From: Jeremie Charrier Date: Tue, 28 Jul 2026 17:24:28 +0200 Subject: [PATCH 1/2] [WALLET-172] Add JAR support Co-Authored-By: Claude Opus 4.8 (1M context) --- CLAUDE.md | 4 +- README.md | 66 ++++- packages/server/package.json | 3 +- packages/server/src/client.ts | 69 +++++- packages/server/src/index.ts | 4 +- packages/server/src/issuer_metadata.ts | 28 +++ packages/server/src/secured_request.ts | 40 +++ packages/server/test/secured_request.test.mjs | 230 ++++++++++++++++++ yarn.lock | 8 + 9 files changed, 435 insertions(+), 17 deletions(-) create mode 100644 packages/server/src/issuer_metadata.ts create mode 100644 packages/server/src/secured_request.ts create mode 100644 packages/server/test/secured_request.test.mjs diff --git a/CLAUDE.md b/CLAUDE.md index f99c352..b23611f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -3,7 +3,7 @@ A Yarn 4 monorepo publishing two ESM TypeScript packages: - **`@proof.com/proof-vc-common`** (`packages/common`) — public/frontend, runs in the browser **and** Node, **zero runtime deps**. Builds OID4VP Authorization Request URLs (`createClient` / `buildAuthorizationUrl`) and reads the `vp_token` from the redirect (`parseAuthorizationResponse`). No secrets, no `nonce` generation, no transaction data. -- **`@proof.com/proof-vc-server`** (`packages/server`) — privileged/backend, **Node only**. Adds `verify` / `verifyVPToken` (SD-JWT-VC verification, X.509 chain validation against an embedded trust root), Pushed Authorization Requests, transaction data, and DC API requests. Depends on `proof-vc-common` and **re-exports it**, so backend integrators need one import. Yarn links it locally via transparent workspaces. +- **`@proof.com/proof-vc-server`** (`packages/server`) — privileged/backend, **Node only**. Adds `verify` / `verifyVPToken` (SD-JWT-VC verification, X.509 chain validation against an embedded trust root), Pushed Authorization Requests, JWT-Secured Authorization Requests (RFC 9101 / "JAR", by value and by reference), transaction data, and signed DC API requests. Depends on `proof-vc-common` and **re-exports it**, so backend integrators need one import. Yarn links it locally via transparent workspaces. `server` reuses `common`'s URL/param builders via the `@proof.com/proof-vc-common/internal` subpath export (server-only; not public frontend surface). @@ -29,7 +29,7 @@ Versioning is **lockstep**: both packages always share one version, published to `packages/server/src` (Node only): - `index.ts` — `export * from "@proof.com/proof-vc-common"` then the server surface (its `createClient` intentionally shadows the frontend one) -- `client.ts` — server `createClient` (PAR, `transactionData`, `dcApiRequest`); `authorizationUrl` is `async` (PAR fetches) +- `client.ts` — server `createClient` (PAR, `transactionData`, RFC 9101 JAR signing via `jose`: `authorizationUrl` by value, `signedAuthorizationRequest` + `jarByReferenceAuthorizationUrl` by reference, `signedDcApiRequest`); `authorizationUrl` is `async` (PAR fetches + JAR signing). Secured requests need `useSecuredAuthorizationRequest: true` and a `privateKeyFactory` (ES256 JWK); `aud` is the AS `issuer` fetched from `AS_METADATA_URL` - `verifier.ts` — `createVerifier` → `verify` / `verifyVPToken` - `transaction_data.ts`, `proof_credentials.ts`, `proof_credential_factory.ts`, `utils.ts`, `types.ts` (`ProofCredential`/`VPToken`/`TrustRoot`), `certificates/**` diff --git a/README.md b/README.md index 69d4921..541f52f 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Read our [documentation](https://dev.proof.com/docs/digital-credentials-overview - [Server Side](#server-side) - [Response Modes](#response-modes) - [Pushed Authorization Requests](#pushed-authorization-requests) + - [Secured Authorization Requests](#secured-authorization-requests) - [Verifiable Credential Presentation](#verifiable-credential-presentation) - [Credential Types](#credential-type) - [Request](#request) @@ -28,10 +29,10 @@ Read our [documentation](https://dev.proof.com/docs/digital-credentials-overview ## Packages -| Package | Runtime | Usage | Runtime deps | -| --------------------------------------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------- | ------------ | -| **[`@proof.com/proof-vc-common`](packages/common)** | browser **or** Node | Request a Verifiable Presentation | **0** ✅ | -| **[`@proof.com/proof-vc-server`](packages/server)** | Node | `proof-vc-common` **plus** Presentation Verification, Pushed Authorization Requests, Transaction Templates | sd-jwt, owf | +| Package | Runtime | Usage | Runtime deps | +| --------------------------------------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------- | +| **[`@proof.com/proof-vc-common`](packages/common)** | browser **or** Node | Request a Verifiable Presentation | **0** ✅ | +| **[`@proof.com/proof-vc-server`](packages/server)** | Node | `proof-vc-common` **plus** Presentation Verification, Pushed Authorization Requests, Secured Authorization Requests (JAR), Transaction Templates | sd-jwt, owf, jose | ## Installation @@ -172,6 +173,63 @@ const redirect = await proof.authorizationUrl({ }); ``` +### Secured Authorization Requests + +Proof supports [JWT-Secured Authorization Requests](https://datatracker.ietf.org/doc/html/rfc9101) (JAR): the Authorization Request parameters are sent as a single signed JWT ("request object") instead of plain query parameters. +JAR is available **only from `@proof.com/proof-vc-server`** and requires: + +- `useSecuredAuthorizationRequest: true` +- a `privateKeyFactory` returning your EC private key as a JWK + +#### By value + +`authorizationUrl` signs the request object and embeds it in the `request` parameter. It can be combined with [Pushed Authorization Requests](#pushed-authorization-requests). + +```javascript +import { createClient } from "@proof.com/proof-vc-server"; + +const proof = createClient({ + environment: "sandbox", + clientId: "caxdw5a7d", + callbackUri: "https://example.com/verify_vp_token", + responseMode: "direct_post", + useSecuredAuthorizationRequest: true, + privateKeyFactory: () => myPrivateKeyJwk, +}); + +const redirect = await proof.authorizationUrl({ + nonce: "3e8e4918-e9fb-453a-a538-81152be15c1b", + scope: "urn:proof:params:scope:verifiable-credentials:basic", +}); +``` + +#### By reference + +If you'd rather host the request object yourself, use `signedAuthorizationRequest` to obtain the signed JWT, store it on your backend, then hand Proof a `request_uri` pointing at it with `jarByReferenceAuthorizationUrl`. +JAR by reference cannot be combined with Pushed Authorization Requests. + +```javascript +const jar = await proof.signedAuthorizationRequest({ + nonce: "3e8e4918-e9fb-453a-a538-81152be15c1b", + scope: "urn:proof:params:scope:verifiable-credentials:basic", +}); + +const redirect = proof.jarByReferenceAuthorizationUrl({ + requestUri: "https://example.com/jar/42", +}); +``` + +#### Digital Credentials API + +For the [W3C Digital Credentials API](https://www.w3.org/TR/digital-credentials/) (`dc_api` response mode), `signedDcApiRequest` returns a signed request object you pass to `navigator.credentials.get`. + +```javascript +const request = await proof.signedDcApiRequest({ + nonce: "3e8e4918-e9fb-453a-a538-81152be15c1b", + dcqlQuery: DCQL_QUERY_BASIC, +}); +``` + ## Verifiable Credential Presentation ### Credential Type diff --git a/packages/server/package.json b/packages/server/package.json index 4ba8627..5b91bea 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -42,7 +42,8 @@ "@owf/identity-common": "^0.3.2", "@proof.com/proof-vc-common": "0.3.1", "@sd-jwt/core": "^0.20.0", - "@sd-jwt/sd-jwt-vc": "^0.20.0" + "@sd-jwt/sd-jwt-vc": "^0.20.0", + "jose": "^6.2.3" }, "devDependencies": { "publint": "^0.3.21", diff --git a/packages/server/src/client.ts b/packages/server/src/client.ts index 3b53dc5..fcd6580 100644 --- a/packages/server/src/client.ts +++ b/packages/server/src/client.ts @@ -8,14 +8,20 @@ import { type AuthorizationRequestParams, } from "@proof.com/proof-vc-common/internal"; import type { DCQLQuery } from "@proof.com/proof-vc-common"; +import type { JWK } from "jose"; import { encodeTransactionData, type TransactionData, } from "./transaction_data.ts"; +import { signRequestObject, requestObjectClaims } from "./secured_request.ts"; + +export type PrivateKeyFactory = () => JWK | Promise; export type ServerClientConfig = ClientConfig & { clientSecret?: string; usePushedAuthorizationRequest?: boolean; + useSecuredAuthorizationRequest?: boolean; + privateKeyFactory?: PrivateKeyFactory; }; export type ServerAuthorizationRequestParams = AuthorizationRequestParams & { @@ -28,7 +34,8 @@ export type DCAPIAuthorizationRequestParams = { transactionData?: TransactionData | string; }; -export type AuthorizationRequest = { +export type DCAPIAuthorizationRequest = { + client_id: string; response_type: typeof RESPONSE_TYPE; response_mode: "dc_api"; nonce: string; @@ -36,9 +43,17 @@ export type AuthorizationRequest = { transaction_data?: string[]; }; +export type JarByReferenceParams = { + requestUri: string; +}; + export interface ServerVCClient { authorizationUrl(params: ServerAuthorizationRequestParams): Promise; - dcApiRequest(params: DCAPIAuthorizationRequestParams): AuthorizationRequest; + signedAuthorizationRequest( + params: ServerAuthorizationRequestParams, + ): Promise; + signedDcApiRequest(params: DCAPIAuthorizationRequestParams): Promise; + jarByReferenceAuthorizationUrl(params: JarByReferenceParams): string; } function encodeTxData( @@ -54,9 +69,6 @@ export function createClient(config: ServerClientConfig): ServerVCClient { params: ServerAuthorizationRequestParams, ): URLSearchParams { const search = buildAuthorizationSearchParams(config, params); - if (config.clientSecret !== undefined) { - search.set("client_secret", config.clientSecret); - } const encoded = encodeTxData(params.transactionData); if (encoded !== undefined) { search.set("transaction_data", encoded); @@ -64,6 +76,12 @@ export function createClient(config: ServerClientConfig): ServerVCClient { return search; } + function signedRequest( + params: ServerAuthorizationRequestParams, + ): Promise { + return signRequestObject(config, requestObjectClaims(buildParams(params))); + } + async function pushAuthorizationRequest( search: URLSearchParams, ): Promise { @@ -72,6 +90,7 @@ export function createClient(config: ServerClientConfig): ServerVCClient { "pushed authorization requests require `clientSecret` in the client config", ); } + search.set("client_secret", config.clientSecret); const parURL = new URL( `${OID4VP_URI}/par`, resolveBaseUrl(config.environment), @@ -108,24 +127,56 @@ export function createClient(config: ServerClientConfig): ServerVCClient { async authorizationUrl( params: ServerAuthorizationRequestParams, ): Promise { - const search = buildParams(params); + const search = + config.useSecuredAuthorizationRequest === true + ? new URLSearchParams({ + client_id: config.clientId, + request: await signedRequest(params), + }) + : buildParams(params); return config.usePushedAuthorizationRequest === true ? pushAuthorizationRequest(search) : authorizeUrlFromSearchParams(config.environment, search); }, - dcApiRequest({ + + signedAuthorizationRequest( + params: ServerAuthorizationRequestParams, + ): Promise { + return signedRequest(params); + }, + + signedDcApiRequest({ dcqlQuery, nonce, transactionData, - }: DCAPIAuthorizationRequestParams): AuthorizationRequest { + }: DCAPIAuthorizationRequestParams): Promise { const encoded = encodeTxData(transactionData); - return { + const request: DCAPIAuthorizationRequest = { + client_id: config.clientId, response_type: RESPONSE_TYPE, response_mode: "dc_api", nonce, dcql_query: dcqlQuery, ...(encoded !== undefined && { transaction_data: [encoded] }), }; + return signRequestObject(config, { ...request }); + }, + + jarByReferenceAuthorizationUrl({ + requestUri, + }: JarByReferenceParams): string { + if (config.usePushedAuthorizationRequest === true) { + throw new Error( + "JAR by reference cannot be combined with pushed authorization requests", + ); + } + return authorizeUrlFromSearchParams( + config.environment, + new URLSearchParams({ + client_id: config.clientId, + request_uri: requestUri, + }), + ); }, }; } diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 6cd9c68..b1c73a0 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -19,10 +19,12 @@ export { TX_DATA_TYPE, transactionData } from "./transaction_data.ts"; export type { ServerClientConfig, + PrivateKeyFactory, ServerAuthorizationRequestParams, ServerVCClient, DCAPIAuthorizationRequestParams, - AuthorizationRequest, + DCAPIAuthorizationRequest, + JarByReferenceParams, } from "./client.ts"; export { createClient } from "./client.ts"; diff --git a/packages/server/src/issuer_metadata.ts b/packages/server/src/issuer_metadata.ts new file mode 100644 index 0000000..edd8dfe --- /dev/null +++ b/packages/server/src/issuer_metadata.ts @@ -0,0 +1,28 @@ +import { resolveBaseUrl } from "@proof.com/proof-vc-common/internal"; +import type { ServerClientConfig } from "./client.ts"; + +const AS_METADATA_URL = + "/.well-known/oauth-authorization-server/verifiable-credentials/v1/issuance"; + +export async function authorizationServerIssuer( + config: ServerClientConfig, +): Promise { + const metadataURL = new URL( + AS_METADATA_URL, + resolveBaseUrl(config.environment), + ).toString(); + const response = await fetch(metadataURL); + const data = (await response.json()) as Record; + if (!response.ok) { + throw new Error( + `failed to fetch authorization server metadata (${response.status})`, + ); + } + const issuer = data["issuer"]; + if (typeof issuer !== "string") { + throw new Error( + "authorization server metadata is missing a string `issuer`", + ); + } + return issuer; +} diff --git a/packages/server/src/secured_request.ts b/packages/server/src/secured_request.ts new file mode 100644 index 0000000..9711dba --- /dev/null +++ b/packages/server/src/secured_request.ts @@ -0,0 +1,40 @@ +import { SignJWT, calculateJwkThumbprint } from "jose"; +import type { ServerClientConfig } from "./client.ts"; +import { authorizationServerIssuer } from "./issuer_metadata.ts"; + +const REQUEST_OBJECT_TYP = "oauth-authz-req+jwt"; +const REQUEST_OBJECT_ALG = "ES256"; + +export function requestObjectClaims( + search: URLSearchParams, +): Record { + return Object.fromEntries(search.entries()); +} + +export async function signRequestObject( + config: ServerClientConfig, + claims: Record, +): Promise { + if (config.useSecuredAuthorizationRequest !== true) { + throw new Error( + "signing a request object requires `useSecuredAuthorizationRequest` in the client config", + ); + } + if (config.privateKeyFactory === undefined) { + throw new Error( + "signing a request object requires `privateKeyFactory` in the client config", + ); + } + const privateKey = await config.privateKeyFactory(); + const kid = await calculateJwkThumbprint(privateKey); + const aud = await authorizationServerIssuer(config); + return new SignJWT(claims) + .setProtectedHeader({ + typ: REQUEST_OBJECT_TYP, + alg: REQUEST_OBJECT_ALG, + kid, + }) + .setIssuer(config.clientId) + .setAudience(aud) + .sign(privateKey); +} diff --git a/packages/server/test/secured_request.test.mjs b/packages/server/test/secured_request.test.mjs new file mode 100644 index 0000000..79aa95a --- /dev/null +++ b/packages/server/test/secured_request.test.mjs @@ -0,0 +1,230 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { + generateKeyPair, + exportJWK, + calculateJwkThumbprint, + jwtVerify, + decodeProtectedHeader, +} from "jose"; + +import { createClient, DCQL_QUERY_BASIC } from "../dist/index.js"; + +const CLIENT_ID = "https://verifier.example.com"; +const CALLBACK_URI = "https://verifier.example.com/callback"; +const ISSUER = "https://api.proof.com"; +const AS_METADATA_URL = + "https://api.proof.com/.well-known/oauth-authorization-server/verifiable-credentials/v1/issuance"; + +const { publicKey, privateKey } = await generateKeyPair("ES256", { + extractable: true, +}); +const privateJwk = await exportJWK(privateKey); +const publicJwk = await exportJWK(publicKey); +const expectedKid = await calculateJwkThumbprint(publicJwk); + +async function withStubbedFetch( + fn, + { issuer = ISSUER, requestUri = "urn:par:123" } = {}, +) { + const realFetch = globalThis.fetch; + const calls = []; + globalThis.fetch = async (url, options) => { + const href = String(url); + calls.push({ url: href, body: options?.body }); + if (href.endsWith("/par")) { + return { + ok: true, + status: 201, + json: async () => ({ request_uri: requestUri }), + }; + } + return { + ok: true, + status: 200, + json: async () => ({ issuer }), + }; + }; + try { + return await fn(calls); + } finally { + globalThis.fetch = realFetch; + } +} + +function securedClient(overrides = {}) { + return createClient({ + environment: "production", + clientId: CLIENT_ID, + callbackUri: CALLBACK_URI, + useSecuredAuthorizationRequest: true, + privateKeyFactory: () => privateJwk, + ...overrides, + }); +} + +test("signedDcApiRequest returns a JAR with the expected header and claims", async () => { + await withStubbedFetch(async (calls) => { + const client = securedClient(); + const jwt = await client.signedDcApiRequest({ + dcqlQuery: DCQL_QUERY_BASIC, + nonce: "nonce-123", + }); + + assert.equal(calls.length, 1); + assert.equal(calls[0].url, AS_METADATA_URL); + + const header = decodeProtectedHeader(jwt); + assert.equal(header.typ, "oauth-authz-req+jwt"); + assert.equal(header.alg, "ES256"); + assert.equal(header.kid, expectedKid); + + const { payload } = await jwtVerify(jwt, publicKey); + assert.equal(payload.iss, CLIENT_ID); + assert.equal(payload.aud, ISSUER); + assert.equal(payload.client_id, CLIENT_ID); + assert.equal(payload.response_type, "vp_token"); + assert.equal(payload.response_mode, "dc_api"); + assert.equal(payload.nonce, "nonce-123"); + assert.deepEqual(payload.dcql_query, DCQL_QUERY_BASIC); + }); +}); + +test("signedAuthorizationRequest signs the plain request params and omits client_secret", async () => { + await withStubbedFetch(async () => { + const client = securedClient({ clientSecret: "s3cret" }); + const jwt = await client.signedAuthorizationRequest({ + scope: "openid", + nonce: "nonce-abc", + state: "state-xyz", + }); + + const { payload } = await jwtVerify(jwt, publicKey); + assert.equal(payload.iss, CLIENT_ID); + assert.equal(payload.aud, ISSUER); + assert.equal(payload.client_id, CLIENT_ID); + assert.equal(payload.scope, "openid"); + assert.equal(payload.nonce, "nonce-abc"); + assert.equal(payload.state, "state-xyz"); + assert.equal(payload.redirect_uri, CALLBACK_URI); + assert.equal(payload.client_secret, undefined); + }); +}); + +test("authorizationUrl embeds the JAR by value without any client_secret", async () => { + await withStubbedFetch(async () => { + const client = securedClient({ clientSecret: "s3cret" }); + const url = await client.authorizationUrl({ + scope: "openid", + nonce: "n", + }); + + const parsed = new URL(url); + assert.equal( + parsed.pathname, + "/verifiable-credentials/v1/presentation/authorize", + ); + assert.equal(parsed.searchParams.get("client_id"), CLIENT_ID); + assert.equal(parsed.searchParams.get("client_secret"), null); + + const jwt = parsed.searchParams.get("request"); + assert.ok(jwt, "expected a `request` JAR param"); + const { payload } = await jwtVerify(jwt, publicKey); + assert.equal(payload.scope, "openid"); + assert.equal(payload.client_secret, undefined); + }); +}); + +test("authorizationUrl over PAR posts the JAR and client_secret in the request body", async () => { + await withStubbedFetch(async (calls) => { + const client = securedClient({ + clientSecret: "s3cret", + usePushedAuthorizationRequest: true, + }); + const url = await client.authorizationUrl({ scope: "openid", nonce: "n" }); + + const parCall = calls.find((c) => c.url.endsWith("/par")); + assert.ok(parCall, "expected a PAR request"); + const body = new URLSearchParams(parCall.body); + assert.equal(body.get("client_id"), CLIENT_ID); + assert.equal(body.get("client_secret"), "s3cret"); + const jwt = body.get("request"); + assert.ok(jwt, "expected the JAR in the PAR body"); + const { payload } = await jwtVerify(jwt, publicKey); + assert.equal(payload.client_secret, undefined); + + const parsed = new URL(url); + assert.equal(parsed.searchParams.get("request_uri"), "urn:par:123"); + assert.equal(parsed.searchParams.get("client_secret"), null); + }); +}); + +test("authorizationUrl never puts client_secret in a non-PAR URL", async () => { + const client = createClient({ + environment: "production", + clientId: CLIENT_ID, + callbackUri: CALLBACK_URI, + clientSecret: "s3cret", + }); + const url = await client.authorizationUrl({ scope: "openid", nonce: "n" }); + + const parsed = new URL(url); + assert.equal(parsed.searchParams.get("client_id"), CLIENT_ID); + assert.equal(parsed.searchParams.get("client_secret"), null); +}); + +test("jarByReferenceAuthorizationUrl builds an authorize URL from client_id and request_uri", () => { + const client = securedClient(); + const url = client.jarByReferenceAuthorizationUrl({ + requestUri: "https://verifier.example.com/jar/42", + }); + + const parsed = new URL(url); + assert.equal( + parsed.pathname, + "/verifiable-credentials/v1/presentation/authorize", + ); + assert.equal(parsed.searchParams.get("client_id"), CLIENT_ID); + assert.equal( + parsed.searchParams.get("request_uri"), + "https://verifier.example.com/jar/42", + ); + assert.equal(parsed.searchParams.get("request"), null); +}); + +test("jarByReferenceAuthorizationUrl rejects JAR over PAR", () => { + const client = securedClient({ usePushedAuthorizationRequest: true }); + assert.throws( + () => + client.jarByReferenceAuthorizationUrl({ + requestUri: "https://verifier.example.com/jar/42", + }), + /cannot be combined with pushed authorization requests/, + ); +}); + +test("signed methods require useSecuredAuthorizationRequest", async () => { + const client = createClient({ + environment: "production", + clientId: CLIENT_ID, + callbackUri: CALLBACK_URI, + privateKeyFactory: () => privateJwk, + }); + await assert.rejects( + client.signedDcApiRequest({ dcqlQuery: DCQL_QUERY_BASIC, nonce: "n" }), + /useSecuredAuthorizationRequest/, + ); +}); + +test("signed methods require privateKeyFactory", async () => { + const client = createClient({ + environment: "production", + clientId: CLIENT_ID, + callbackUri: CALLBACK_URI, + useSecuredAuthorizationRequest: true, + }); + await assert.rejects( + client.signedDcApiRequest({ dcqlQuery: DCQL_QUERY_BASIC, nonce: "n" }), + /privateKeyFactory/, + ); +}); diff --git a/yarn.lock b/yarn.lock index cf2bcdb..f7655f4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -221,6 +221,7 @@ __metadata: "@proof.com/proof-vc-common": "npm:0.3.1" "@sd-jwt/core": "npm:^0.20.0" "@sd-jwt/sd-jwt-vc": "npm:^0.20.0" + jose: "npm:^6.2.3" publint: "npm:^0.3.21" typescript: "npm:^6.0.3" languageName: unknown @@ -872,6 +873,13 @@ __metadata: languageName: node linkType: hard +"jose@npm:^6.2.3": + version: 6.2.3 + resolution: "jose@npm:6.2.3" + checksum: 10c0/aa91bccba22cc84d86308f833749bcb0b00441e35c24e0ac79abeac5f76dc62d47bdef9c1da6a0c609f5da6478595f52b252085888b89dbdb163861e40ea4188 + languageName: node + linkType: hard + "json-buffer@npm:3.0.1": version: 3.0.1 resolution: "json-buffer@npm:3.0.1" From 203457e41eceb11a256374e8cf7d28ce474098fa Mon Sep 17 00:00:00 2001 From: Jeremie Charrier Date: Thu, 30 Jul 2026 14:01:46 +0200 Subject: [PATCH 2/2] wip Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/server/src/issuer_metadata.ts | 2 +- packages/server/test/secured_request.test.mjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server/src/issuer_metadata.ts b/packages/server/src/issuer_metadata.ts index edd8dfe..17afd29 100644 --- a/packages/server/src/issuer_metadata.ts +++ b/packages/server/src/issuer_metadata.ts @@ -2,7 +2,7 @@ import { resolveBaseUrl } from "@proof.com/proof-vc-common/internal"; import type { ServerClientConfig } from "./client.ts"; const AS_METADATA_URL = - "/.well-known/oauth-authorization-server/verifiable-credentials/v1/issuance"; + "/.well-known/oauth-authorization-server/verifiable-credentials/v1/presentation"; export async function authorizationServerIssuer( config: ServerClientConfig, diff --git a/packages/server/test/secured_request.test.mjs b/packages/server/test/secured_request.test.mjs index 79aa95a..fe550d1 100644 --- a/packages/server/test/secured_request.test.mjs +++ b/packages/server/test/secured_request.test.mjs @@ -14,7 +14,7 @@ const CLIENT_ID = "https://verifier.example.com"; const CALLBACK_URI = "https://verifier.example.com/callback"; const ISSUER = "https://api.proof.com"; const AS_METADATA_URL = - "https://api.proof.com/.well-known/oauth-authorization-server/verifiable-credentials/v1/issuance"; + "https://api.proof.com/.well-known/oauth-authorization-server/verifiable-credentials/v1/presentation"; const { publicKey, privateKey } = await generateKeyPair("ES256", { extractable: true,