OpenID4VCI 1.0 Wallet (Holder) issuance flow.
request_credential/3 drives the pre-authorized_code issuance flow end to
end:
- exchange the offer's pre-authorized code for an access token
(
AttestoClient.Token.exchange_pre_authorized_code/2) - skipped when an:access_tokenis already supplied, e.g. obtained separately through the authorization_code flow (AttestoClient.AuthorizationCode); - fetch a fresh
c_noncewhen the issuer advertises a nonce endpoint; - build the holder key proof (
AttestoClient.Wallet.Proof); - POST the Credential Request and parse the Credential Response
(
Attesto.CredentialResponse), decrypting an encrypted response when negotiated with the issuer; a deferred response is polled when a Deferred Credential Endpoint is supplied; and - verify each returned credential with the attesto verifier matching
:format(Attesto.SdJwtVc,Attesto.JwtVc, orAttesto.Mdoc).
All HTTP goes through AttestoClient.OAuthHTTP, so the flow is mockable
the same way as the rest of this library (req_options: [plug: ...]).
Summary
Functions
Run the wallet-holder issuance flow for offer and return the verified,
held credential(s) (or a pending marker for deferred issuance).
Types
@type opt() :: {:credential_configuration_id, String.t()} | {:credential_endpoint, String.t()} | {:credential_issuer_metadata, map()} | {:credential_encryption, :auto | :required | :disabled} | {:credential_response_encryption_key, AttestoClient.Wallet.Proof.jwk()} | {:deferred_credential_endpoint, String.t()} | {:deferred_poll, boolean()} | {:deferred_timeout, pos_integer()} | {:deferred_max_attempts, pos_integer()} | {:deferred_clock, (-> integer())} | {:deferred_sleep, (non_neg_integer() -> :ok | {:error, term()})} | {:token_endpoint, String.t()} | {:nonce_endpoint, String.t()} | {:notification_endpoint, String.t()} | {:notification_event, String.t()} | {:access_token, String.t() | AttestoClient.TokenSet.t()} | {:tx_code, String.t()} | {:client_id, String.t()} | {:client_auth, term()} | {:format, String.t()} | {:trusted, term()} | {:haip, boolean()} | {:trusted_certificates, [binary()]} | {:certificate_trust, ([binary()] -> :ok | {:error, term()})} | {:verify_opts, keyword()} | {:proof_alg, String.t()} | {:proof_kid, String.t()} | {:key_attestation, String.t() | ([map()], String.t() | nil -> {:ok, String.t()} | {:error, term()})} | {:dpop, AttestoClient.Wallet.Proof.jwk()} | {:alg, String.t()} | {:kid, String.t()} | {:now, integer()} | {:req_options, keyword()} | {:timeout, pos_integer()}
@type result() :: %{ notification_id: String.t() | nil, credentials: [held_credential() | pending_credential()], c_nonce: String.t() | nil }
Functions
@spec request_credential( AttestoClient.Wallet.CredentialOffer.t(), AttestoClient.Wallet.Proof.jwk() | [AttestoClient.Wallet.Proof.jwk()], [opt()] ) :: {:ok, result()} | {:error, term()}
Run the wallet-holder issuance flow for offer and return the verified,
held credential(s) (or a pending marker for deferred issuance).
Required options: :credential_endpoint, :format (one of "vc+sd-jwt",
"dc+sd-jwt", "jwt_vc_json", or "mso_mdoc"), and :trusted (the
Credential Issuer's verification key material, in the shape the matching
attesto verifier expects - a JWKS/JWK list for Attesto.SdJwtVc and
Attesto.JwtVc, a single JWK/PEM for Attesto.Mdoc). :verify_opts are
forwarded to that verifier (e.g. :accepted_algs, :now).
Unless :access_token is supplied, the flow performs the
pre-authorized_code token exchange itself and requires :token_endpoint
and, when the offer's grant carries one, :tx_code. :client_id and
:client_auth authenticate that token request exactly as in
AttestoClient.Token, and, when present, :client_id is also carried as
the proof's iss.
:nonce_endpoint, when supplied, is used to fetch a fresh c_nonce before
building the proof. :credential_configuration_id selects which of the
offer's configuration ids to request; it defaults to the offer's only id
when there is exactly one.
:dpop (a JOSE.JWK or JWK map) sender-constrains the flow with RFC 9449
DPoP proofs. The one key is used at the token endpoint (binding the access
token to its jkt) and at the Credential Endpoint (where the proof carries
the ath binding to that token), and a use_dpop_nonce challenge is retried
once - see AttestoClient.DPoP. :alg and :kid select the DPoP proof's
signing algorithm and key identifier; :proof_alg and :proof_kid configure
the separate OID4VCI credential proof.
HAIP requires DPoP and authenticated token acquisition. When supplying an
existing token, pass access_token: tokens with the complete locally bound
AttestoClient.TokenSet and the same DPoP private key. Bare token strings
remain available for generic flows only. A retained HAIP profile cannot be
disabled by passing haip: false to credential issuance.
:key_attestation (a compact JWT from AttestoClient.KeyAttestation.build/2)
is carried in the holder proof's key_attestation header, vouching to the
issuer that the holder key is held in secure storage (a HAIP requirement).
:notification_endpoint, when supplied, makes the wallet POST an OID4VCI §11
Notification acknowledging the credential once issuance succeeds and the
response carried a notification_id; :notification_event overrides the
default "credential_accepted". The returned result always includes the
issuer's notification_id (or nil).
:credential_issuer_metadata accepts metadata already fetched and validated
by the caller. Its credential_issuer must equal the offer's issuer. Endpoint
options may be omitted when present in this metadata; conflicting endpoint
overrides are rejected. Encryption uses ECDH-ES/P-256 and A128GCM or A256GCM.
:credential_encryption defaults to :auto, which requests encryption when
advertised. :required requires encrypted requests and responses; :disabled
is allowed only when the issuer does not require encryption. Response
encryption always also encrypts the request, as required by §8.2.
:deferred_credential_endpoint enables bounded polling of a deferred result.
:deferred_poll can be set to false to retain a pending marker. Polling
respects the issuer's positive interval, with at most 10 attempts and a
120-second overall deadline by default. :deferred_max_attempts (1–100) and
:deferred_timeout (milliseconds, at most one hour) customize these bounds;
:deferred_clock and :deferred_sleep provide injectable monotonic clock and
wait functions. The response encryption key remains private to this flow.