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:
- 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.
- 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.