@openvtc/rp-sdk
v0.2.0
Published
Server-side SDK for Relying Parties consuming SIOPv2 id_tokens from the OpenVTC browser plugin
Maintainers
Readme
@openvtc/rp-sdk
Server-side SDK for Relying Parties (RPs) consuming SIOPv2 id_tokens
from the OpenVTC browser plugin (window.vtaWallet.login).
What this package does
window.vtaWallet.login() POSTs a SIOPv2 self-issued id_token to
your /auth/ endpoint. The token is a compact EdDSA JWS signed by
the wallet's holder did:key. The signature is not optional —
without verifying it, any page can forge a login as any DID.
This SDK is the audited verification path. verifyIdToken:
- pins
algtoEdDSA(no algorithm substitution), - enforces SIOPv2
iss === sub, - pins
audto your RP DID (no leniency), - pins
nonceto the challenge you issued (constant-time match), - checks
iat/expwithin a configurable clock-skew window, - resolves the issuer DID and verifies the JWS signature against the resolved Ed25519 verification method.
Failure modes surface as IdTokenVerificationError with a typed
reason — log it so operators can distinguish misconfigured
audience from a forged token.
Why this exists
The browser-plugin demo skips verification — it trusts whatever the wallet POSTs. The demo is widely copy-pasted into production code, inheriting the gap. The May 2026 OpenVTC security review flagged this as a high-severity issue (H2). This SDK is the fix.
Install
npm install @openvtc/rp-sdkUsage
Verify an id_token
import { verifyIdToken, KeyResolver } from "@openvtc/rp-sdk";
const resolver = new KeyResolver(); // did:key only; see below
const verified = await verifyIdToken({
idToken: req.body.id_token,
audience: process.env.RP_DID!,
nonce: sessionStore.challengeFor(req.body.session_id),
resolver,
});
// verified.subject is the wallet's holder DID; bind your session to it.
console.log(`logged in: ${verified.subject}`);Establish a session cookie
import { establishSession } from "@openvtc/rp-sdk";
const accessToken = await myJwtMinter.mint({ sub: verified.subject });
const { subject, cookie } = establishSession(verified, accessToken);
res.cookie(cookie.name, cookie.value, cookie.options);
// SDK sets HttpOnly + Secure + SameSite=Strict by default.DID resolvers
The bundled KeyResolver handles did:key:z6Mk… (Ed25519 multikey)
in-process — no network round-trip, no cache concerns.
For did:peer:2 (the wallet's default for inbound RP-initiated
flows), did:webvh, or did:web — implement the DidResolver
interface against your preferred resolver. A thin wrapper around
affinidi-did-resolver-cache-sdk covers all of them.
import type { DidResolver } from "@openvtc/rp-sdk";
class MultiMethodResolver implements DidResolver {
async resolveAuthenticationKey(did: string): Promise<Uint8Array> {
if (did.startsWith("did:key:")) return keyResolver.resolveAuthenticationKey(did);
// ... did:peer / did:webvh / did:web cases
}
}Roadmap
Planned for follow-up minor versions:
requireStepUp()middleware — gates routes behindacr=aal2.refreshProxy()middleware — drop-in/auth/refreshproxy.- Express + Fastify + Hono framework adapters.
- DIDComm-transport variant for RPs that prefer the wallet's authcrypt flow over the REST SIOPv2 flow.
License
Apache-2.0
