Verifiable credentials

Truora Pass can package a user’s verified identity into a verifiable credential: a cryptographically signed document that the user holds in their own Truora Pass wallet and presents to your application on demand. Unlike the OAuth 2.0 flows — where your application reads identity data from the UserInfo endpoint on every access — a verifiable credential is verified locally, against Truora Pass’s published public keys. Once you hold a presentation, no Truora API call is required to check that the data is authentic.

Truora Pass issues credentials in the SD-JWT VC format (IETF SD-JWT-based Verifiable Credentials), following the OpenID for Verifiable Credential Issuance (OpenID4VCI) and OpenID for Verifiable Presentations (OpenID4VP) profiles.

The trust triangle

Verifiable credentials involve three roles:

Role Who What they do
Issuer Truora Pass Verifies the user’s identity (document + face verification), signs a credential over the verified claims, and publishes its public keys and revocation status lists.
Holder The user, through the Truora Pass wallet Stores the credential on their own device and decides — claim by claim — what to disclose, and to whom.
Verifier Your application Receives a presentation from the holder and verifies the issuer’s signature, the holder’s proof of possession, and the revocation status — all cryptographically.

The issuer and the verifier never need to talk to each other at presentation time: the verifier trusts the issuer’s signature, not a live API response. See Verifying credential presentations for the verifier integration.

The SD-JWT VC format

A Truora Pass credential is a compact JWT signed by Truora Pass with ES256 (ECDSA over the P-256 curve), with the JOSE header typ: dc+sd-jwt and a kid that references a key in the Truora Pass JWKS. The credential travels as the signed JWT followed by ~-separated disclosures:

<issuer-signed JWT>~<disclosure 1>~<disclosure 2>~…~<disclosure N>~

The issuer-signed JWT payload contains the registered claims plus digests of the selectively disclosable claims:

{
  "iss": "https://pass.truora.com",
  "vct": "urn:truora:pass:identity:1",
  "iat": 1719945600,
  "exp": 1751481600,
  "cnf": {
    "jwk": { "kty": "EC", "crv": "P-256", "x": "<holder key x>", "y": "<holder key y>" }
  },
  "status": {
    "status_list": {
      "idx": 42,
      "uri": "https://api.pass.truora.com/v1/credentials/status-lists/identity-v1"
    }
  },
  "_sd": ["<digest 1>", "<digest 2>", "…"],
  "_sd_alg": "sha-256"
}
Claim Meaning
iss The credential issuer — always https://pass.truora.com
vct The credential type — urn:truora:pass:identity:1 for the identity credential
iat / exp Issued-at and expiration times
cnf.jwk The holder’s public key (holder binding)
status.status_list Where to check revocation: a status list URI and this credential’s index in it
_sd / _sd_alg Digests of the disclosable claims, and the digest algorithm (sha-256)

Selective disclosure

The identity claims are not in the signed payload in clear text. Each one is a separate disclosure — the base64url encoding of a JSON array [<salt>, <claim name>, <claim value>]:

["6qMQvRL5haj…", "given_name", "Ana"]  →  base64url  →  WyI2cU1RdlJMNWhhai4uLiIsICJnaXZlbl9uYW1lIiwgIkFuYSJd

Only the SHA-256 digest of each disclosure appears in the signed _sd array. When the user presents the credential, the wallet attaches only the disclosures the user approved — the rest of the claims stay hidden, while the issuer’s signature remains verifiable. A verifier recomputes each attached disclosure’s digest and matches it against _sd, so disclosed values cannot be forged or altered.

This is what makes the credential privacy-preserving: the user can prove their given_name and family_name to one application and additionally their document_number to another, from the same signed credential.

The identity credential

Truora Pass issues one credential type, vct: urn:truora:pass:identity:1, over the user’s verified identity. Issuance requires the user to have, inside Truora Pass:

  • a verified, unexpired identity document, and
  • a completed face verification.

The credential carries these selectively disclosable claims, sourced from the verified document:

Claim Description
given_name Given name(s), as printed on the verified document
family_name Family name(s), as printed on the verified document
birthdate Date of birth, YYYY-MM-DD
document_number The verified document’s number
document_type The verified document’s type (for example national-id, passport)
country The document’s issuing country
nationality The user’s nationality

Note: the credential’s expiration never outlives the document it was issued from — exp is capped at the verified document’s expiration date (and at most one year from issuance). Users can re-issue at any time; re-issuing revokes the previous identity credential, so each user has at most one active identity credential.

Holder binding

Every credential is bound to a key pair generated on the user’s device when the credential is issued. The public key is embedded in the signed credential as cnf.jwk; the private key is non-extractable and never leaves the device.

When the user presents the credential, the wallet appends a Key Binding JWT (KB-JWT) — a short JWT with header typ: kb+jwt, signed with the holder’s private key, containing the verifier’s nonce, the audience, and sd_hash (a digest of the exact presented credential and disclosures). The KB-JWT proves the presenter possesses the credential’s bound key: a copied or leaked SD-JWT cannot be replayed by anyone else, and a captured presentation cannot be replayed against a different request, because each nonce is single-use.

Revocation

Truora Pass publishes signed status lists (Token Status List format). Each credential’s status.status_list claim points to a list URI and the credential’s bit index in that list. The list endpoint is public and requires no authentication:

curl https://api.pass.truora.com/v1/credentials/status-lists/identity-v1

It returns a compact JWT (Content-Type: application/statuslist+jwt) signed by Truora Pass, whose payload carries one bit per credential — bit set means revoked. A credential is revoked when the user deletes it from their wallet, or automatically when they re-issue a new one.

Verifiers must check the status list as part of verification — a presentation of a revoked credential is rejected with the credential_revoked error code. See Verifying credential presentations for the full verification procedure.

How presentations reach your application

There are two ways your application can receive a credential presentation:

  1. You request it (OpenID4VP) — your backend creates a presentation request with the claims you need; the user approves (or trims) the disclosure in Truora Pass, and you collect the verified result. This is the integration described in Verifying credential presentations.
  2. The user shares it (share link + QR) — the user creates a share link or QR code from their wallet, choosing which claims to include; anyone with the link sees the verified claims and can independently verify the raw presentation. See Issuing and sharing credentials.