@gramota/oid4vp
OID4VP wire format — request, response, signed JAR (RFC 9101), x509_san_dns cert helpers.
Install: pnpm add @gramota/oid4vp
Source: github.com/gramota-org/gramota/tree/main/packages/oid4vp
Classes
Oid4vpError
Defined in: @gramota/oid4vp/dist/types.d.ts:112
Extends
GramotaError
Constructors
Constructor
new Oid4vpError(
code: Oid4vpErrorCode,
message: string,
options?: {
cause?: unknown;
}): Oid4vpError;
Defined in: @gramota/oid4vp/dist/types.d.ts:114
Parameters
code
message
string
options?
cause?
unknown
Returns
Overrides
GramotaError.constructor
Properties
cause?
readonly optional cause?: unknown;
Defined in: .pnpm/@gramota+core@0.2.1/node_modules/@gramota/core/dist/error.d.ts:44
Optional original error that caused this one. Always set when the
Gramota package is wrapping a thrown exception from a dependency
(Web Crypto, JOSE, fetch). Survives JSON.stringify(err) only via
the cause property — Node 16.9+ logs it natively.
Inherited from
GramotaError.cause
code
readonly code: Oid4vpErrorCode;
Defined in: @gramota/oid4vp/dist/types.d.ts:113
Stable string that identifies the failure mode. Subclasses narrow the type; at runtime it's always a string. Use for branching, logs, and metrics labels — never serialize GramotaError.message for that purpose, message strings drift across versions.
Overrides
GramotaError.code
name
name: string;
Defined in: .pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es5.d.ts:1076
Inherited from
GramotaError.name
message
message: string;
Defined in: .pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es5.d.ts:1077
Inherited from
GramotaError.message
stack?
optional stack?: string;
Defined in: .pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es5.d.ts:1078
Inherited from
GramotaError.stack
Interfaces
GenerateSigningCertOptions
Defined in: @gramota/oid4vp/dist/cert.d.ts:22
Properties
sanDns
readonly sanDns: string;
Defined in: @gramota/oid4vp/dist/cert.d.ts:26
Primary DNS name for the cert's Subject Alternative Name. Must match
the verifier's hostname (the part the wallet sees in the OID4VP
client_id after the x509_san_dns: prefix).
extraSanDns?
readonly optional extraSanDns?: readonly string[];
Defined in: @gramota/oid4vp/dist/cert.d.ts:30
Additional SAN-DNS entries — used for wildcards (e.g. *.localtest.me)
so a single cert covers per-tenant subdomains, or for serving multiple
verifier hostnames behind one identity.
commonName?
readonly optional commonName?: string;
Defined in: @gramota/oid4vp/dist/cert.d.ts:33
Cert subject CN — purely cosmetic, shown in cert viewers. Defaults
to sanDns.
organizationName?
readonly optional organizationName?: string;
Defined in: @gramota/oid4vp/dist/cert.d.ts:35
Org name for the cert subject. Defaults to "Gramota Verifier".
validDays?
readonly optional validDays?: number;
Defined in: @gramota/oid4vp/dist/cert.d.ts:37
Validity in days. Default 365.
SignAuthorizationRequestOptions
Defined in: @gramota/oid4vp/dist/jar.d.ts:22
Properties
request
readonly request: AuthorizationRequest;
Defined in: @gramota/oid4vp/dist/jar.d.ts:26
The verifier's Authorization Request to sign. Must already include
the verifier-side fields (client_id, nonce, response_uri, etc).
The signer doesn't validate semantic content — only wire-encodes.
cert
readonly cert: SigningCert;
Defined in: @gramota/oid4vp/dist/jar.d.ts:29
The verifier's signing material — produced by generateSigningCert
or supplied externally.
AuthorizationRequest
Defined in: @gramota/oid4vp/dist/types.d.ts:19
OID4VP §5 — Authorization Request, the bundle a verifier sends to a wallet.
In production this is delivered as a query string, a request_uri (signed
JWT), or via a custom URI scheme like openid4vp://. We model the parsed
structure once and let serialisation handle the rest.
Properties
response_type
response_type: "vp_token";
Defined in: @gramota/oid4vp/dist/types.d.ts:21
MUST be vp_token per OID4VP §5.
client_id
client_id: string;
Defined in: @gramota/oid4vp/dist/types.d.ts:23
Identifier of the verifier — meaning depends on client_id_scheme.
client_id_scheme?
optional client_id_scheme?: ClientIdScheme;
Defined in: @gramota/oid4vp/dist/types.d.ts:26
How the wallet should resolve and trust client_id. Default
pre-registered — for HAIP/EUDIW the spec mandates explicit.
response_mode?
optional response_mode?: ResponseMode;
Defined in: @gramota/oid4vp/dist/types.d.ts:28
How the wallet returns the response. HAIP requires direct_post.
response_uri?
optional response_uri?: string;
Defined in: @gramota/oid4vp/dist/types.d.ts:30
Where to POST the response when response_mode=direct_post.
redirect_uri?
optional redirect_uri?: string;
Defined in: @gramota/oid4vp/dist/types.d.ts:32
Where to redirect the response for legacy modes.
nonce
nonce: string;
Defined in: @gramota/oid4vp/dist/types.d.ts:34
Cryptographic challenge — bound into KB-JWT to prevent replay.
state?
optional state?: string;
Defined in: @gramota/oid4vp/dist/types.d.ts:36
Opaque verifier-controlled correlation token, echoed back unchanged.
presentation_definition?
optional presentation_definition?: Readonly<Record<string, unknown>>;
Defined in: @gramota/oid4vp/dist/types.d.ts:39
Inline DIF Presentation Definition JSON — what the verifier wants (OID4VP 1.0 query shape).
presentation_definition_uri?
optional presentation_definition_uri?: string;
Defined in: @gramota/oid4vp/dist/types.d.ts:41
Or a URL the wallet can fetch the PD from. Mutually exclusive with above.
dcql_query?
optional dcql_query?: Readonly<Record<string, unknown>>;
Defined in: @gramota/oid4vp/dist/types.d.ts:44
DCQL query (OID4VP 2.0 — Digital Credentials Query Language).
Mutually exclusive with presentation_definition.
client_metadata?
optional client_metadata?: Readonly<Record<string, unknown>>;
Defined in: @gramota/oid4vp/dist/types.d.ts:46
Wallet metadata transparency parameter.
scope?
optional scope?: string;
Defined in: @gramota/oid4vp/dist/types.d.ts:49
Subset of the wallet's supported response formats / curves the verifier is willing to accept.
AuthorizationResponse
Defined in: @gramota/oid4vp/dist/types.d.ts:65
OID4VP §6 — Authorization Response from wallet to verifier.
The wire shape depends on which query language the request used:
- Presentation Exchange (DIF PEX):
vp_tokenis a string or string[], paired withpresentation_submissionmapping descriptors to vp_token positions. - DCQL (OID4VP Final 1.0):
vp_tokenis a JSON OBJECT keyed by the DCQL credentialid(e.g.{"pid": "). No"} presentation_submissionis sent — the keys ARE the mapping.
Verifiers should accept whichever shape the wallet sends; production EU wallets (eudi-lib-android-wallet-ui 0.26+) use DCQL exclusively.
Properties
vp_token
vp_token:
| string
| readonly string[]
| Readonly<Record<string, string>>;
Defined in: @gramota/oid4vp/dist/types.d.ts:72
The presentation(s).
- String / string[] form (PEX response).
- Object form (DCQL response) — keys are the DCQL credential ids and values are the credential strings.
presentation_submission?
optional presentation_submission?: Readonly<Record<string, unknown>>;
Defined in: @gramota/oid4vp/dist/types.d.ts:75
DIF Presentation Submission mapping descriptors → vp_token positions. Required for PEX responses; absent for DCQL responses.
state?
optional state?: string;
Defined in: @gramota/oid4vp/dist/types.d.ts:77
Echoes the verifier's state from the request.
iss?
optional iss?: string;
Defined in: @gramota/oid4vp/dist/types.d.ts:79
OID4VP §6.4 — the wallet's identifier (e.g. issuer URL).
SigningCert
Defined in: @gramota/oid4vp/dist/types.d.ts:97
X.509 signing material for an OID4VP verifier.
Bundles together the four artefacts a verifier needs to produce signed
Authorization Requests (RFC 9101 JAR) and prove its identity to wallets
via the x509_san_dns client_id_prefix:
privateKeyPem— PKCS#8 private key, used to sign the JARcertificatePem— leaf cert, served when the wallet asks for proofx5c— base64 DER cert(s) embedded in the JWSx5cheadersanDns— the SAN-DNS hostname the wallet matches againstclient_id
Generation: see generateSigningCert for self-signed certs (local dev,
pinned-trust-store deployments). Production typically uses an externally
issued cert (ACME, corporate CA) — same shape, different origin.
Properties
privateKeyPem
readonly privateKeyPem: string;
Defined in: @gramota/oid4vp/dist/types.d.ts:99
PEM-encoded PKCS#8 private key.
certificatePem
readonly certificatePem: string;
Defined in: @gramota/oid4vp/dist/types.d.ts:101
PEM-encoded leaf certificate.
x5c
readonly x5c: readonly string[];
Defined in: @gramota/oid4vp/dist/types.d.ts:104
Base64-encoded DER certs in chain order, suitable for the JWS x5c
header per RFC 7515 §4.1.6. Length 1 for self-signed leaves.
sanDns
readonly sanDns: string;
Defined in: @gramota/oid4vp/dist/types.d.ts:108
DNS name in the cert's Subject Alternative Name. The wallet
compares this against the OID4VP client_id value (with the
x509_san_dns: prefix stripped).
Type Aliases
ClientIdScheme
type ClientIdScheme =
| "pre-registered"
| "redirect_uri"
| "https"
| "did"
| "x509_san_dns"
| "x509_san_uri"
| "verifier_attestation"
| string & {
};
Defined in: @gramota/oid4vp/dist/types.d.ts:11
OID4VP §5.4 — client_id_scheme enumerated values.
ResponseMode
type ResponseMode = "direct_post" | "direct_post.jwt" | "fragment" | "query";
Defined in: @gramota/oid4vp/dist/types.d.ts:13
OID4VP §6.2 — response_mode for the authorization response.
Oid4vpErrorCode
type Oid4vpErrorCode =
| "oid4vp.invalid_url"
| "oid4vp.required_field_missing"
| "oid4vp.unsupported_response_type"
| "oid4vp.mutually_exclusive_fields"
| "oid4vp.response_uri_required"
| "oid4vp.invalid_json"
| "oid4vp.invalid_value_type"
| "oid4vp.malformed_body"
| "oid4vp.malformed_submission"
| "oid4vp.cert_generation_failed"
| "oid4vp.jar_signing_failed";
Defined in: @gramota/oid4vp/dist/types.d.ts:111
Stable codes for Oid4vpError.
Functions
generateSigningCert()
function generateSigningCert(input: GenerateSigningCertOptions): Promise<SigningCert>;
Defined in: @gramota/oid4vp/dist/cert.d.ts:53
Generate an ES256 keypair + self-signed X.509 cert with the given SAN-DNS entries.
The cert carries the standard verifier-identity extensions: serverAuth
- clientAuth EKUs (so wallets that look for a TLS-style cert accept it), digitalSignature + keyEncipherment KU flags, and CA:false.
Returns a SigningCert ready to feed into signAuthorizationRequest.
Parameters
input
Returns
Promise<SigningCert>
Throws
Oid4vpError with oid4vp.cert_generation_failed if
the underlying crypto / X.509 generation fails.
signingCertToJwks()
function signingCertToJwks(cert: SigningCert): Promise<{
publicJwk: JsonWebKey;
privateJwk: JsonWebKey;
}>;
Defined in: @gramota/oid4vp/dist/cert.d.ts:68
Convert a SigningCert (PEM bundle) into the JWK pair that
@gramota/issuer and other JOSE consumers expect.
Unlike generateSigningCert this is a pure conversion — it doesn't
touch crypto state or generate anything new. It just bridges the two
representations of the same key material:
- Input shape: PKCS#8 PEM (private) + X.509 PEM (public-via-cert)
- Output shape: two RFC 7517 JWKs with
alg: "ES256"set
Throws Oid4vpError with oid4vp.cert_generation_failed on a
malformed PEM or an unrecognised key type.
Parameters
cert
Returns
Promise<{
publicJwk: JsonWebKey;
privateJwk: JsonWebKey;
}>
signAuthorizationRequest()
function signAuthorizationRequest(options: SignAuthorizationRequestOptions): Promise<string>;
Defined in: @gramota/oid4vp/dist/jar.d.ts:49
Sign an OID4VP Authorization Request as a compact-serialised JWS, formatted per RFC 9101 + OID4VP §5.10.
The JWS header carries:
alg: "ES256"(the only algorithm the EU wallet currently accepts for x509_san_dns prefix; future iterations can lift this)typ: "oauth-authz-req+jwt"per RFC 9101x5c: [so the wallet doesn't need a separate key-discovery step]
The JWS payload IS the request object — claims at the top level, not
wrapped in some envelope (RFC 9101 §10.2). Caller serves the result
with content type application/oauth-authz-req+jwt.
Parameters
options
SignAuthorizationRequestOptions
Returns
Promise<string>
Throws
Oid4vpError with oid4vp.jar_signing_failed if the
private key is malformed or signing fails.
buildAuthorizationRequestUrl()
function buildAuthorizationRequestUrl(baseUrl: string, request: AuthorizationRequest): string;
Defined in: @gramota/oid4vp/dist/request.d.ts:33
Serialise an Authorization Request to a URL with query parameters per OID4VP §5.1 (parameter encoding rules).
The base URL is typically a custom scheme like openid4vp:// for the
native wallet handoff, or https://wallet.example.com/authorize for
web flows — both work, the function just appends the query string.
Object-valued parameters (presentation_definition, dcql_query,
client_metadata) are JSON-encoded into the query string per §5.1.
For HAIP / EUDIW-compliant verifiers, this output is typically wrapped
in a signed JAR (RFC 9101) via signAuthorizationRequest and
delivered as request= rather than as raw query params.
Parameters
baseUrl
string
request
Returns
string
Example
const url = buildAuthorizationRequestUrl("openid4vp://", {
response_type: "vp_token",
client_id: "x509_san_dns:verifier.example",
nonce: "n-12345",
response_mode: "direct_post",
response_uri: "https://verifier.example/oid4vp/response",
dcql_query: { credentials: [{ id: "pid", format: "dc+sd-jwt", meta: {...} }] },
});
Throws
Oid4vpError oid4vp.required_field_missing,
oid4vp.unsupported_response_type, oid4vp.mutually_exclusive_fields,
oid4vp.response_uri_required, or oid4vp.invalid_value_type.
parseAuthorizationRequestUrl()
function parseAuthorizationRequestUrl(rawUrl: string): AuthorizationRequest;
Defined in: @gramota/oid4vp/dist/request.d.ts:45
Parse an OID4VP Authorization Request from a URL string.
Inverse of buildAuthorizationRequestUrl. Validates required fields and mutually-exclusive flag combinations; does NOT verify a signed JAR (use the wallet's JWS verification step for that, then call this function on the JAR's payload claims).
Parameters
rawUrl
string
Returns
Throws
Oid4vpError with oid4vp.invalid_url,
oid4vp.required_field_missing, or other field-validation codes.
parseAuthorizationRequestSearchParams()
function parseAuthorizationRequestSearchParams(params: URLSearchParams | Record<string, string>): AuthorizationRequest;
Defined in: @gramota/oid4vp/dist/request.d.ts:59
Parse an OID4VP Authorization Request from URL query parameters.
Useful when the verifier already has parsed query params from a web
framework (req.query from Express, Hono, Fastify, ...). Accepts
either a URLSearchParams instance or a plain Record.
Object-valued params (presentation_definition, dcql_query,
client_metadata) are JSON-decoded; everything else is left as-is.
Parameters
params
URLSearchParams | Record<string, string>
Returns
Throws
Oid4vpError with oid4vp.invalid_json if a JSON-valued
param is malformed, plus the standard field-validation codes.
buildAuthorizationResponseBody()
function buildAuthorizationResponseBody(response: AuthorizationResponse): string;
Defined in: @gramota/oid4vp/dist/response.d.ts:13
Serialise an Authorization Response to a URL-encoded form body, suitable
for direct_post (POST application/x-www-form-urlencoded). Per OID4VP §6:
vp_tokenis the raw presentation string, a JSON-encoded array if multiple credentials are presented (PEX), or a JSON-encoded object keyed by DCQL credential id (DCQL response form).presentation_submissionis JSON-encoded — only included for PEX responses; DCQL responses omit it.state(optional) andiss(optional) pass through as strings.
Parameters
response
Returns
string
parseAuthorizationResponseBody()
function parseAuthorizationResponseBody(rawBody: string): AuthorizationResponse;
Defined in: @gramota/oid4vp/dist/response.d.ts:15
Parse an Authorization Response from a URL-encoded form body string.
Parameters
rawBody
string
Returns
parseAuthorizationResponseFromParams()
function parseAuthorizationResponseFromParams(params: URLSearchParams | Record<string, string>): AuthorizationResponse;
Defined in: @gramota/oid4vp/dist/response.d.ts:18
Parse from URLSearchParams or a plain object — handy when frameworks have already decoded the body.
Parameters
params
URLSearchParams | Record<string, string>