Client attestation challenges

Issuer2 can require a signed challenge inside the client-attestation proof of possession. Challenges are off unless you enable them. Key attestations inside credential proofs are a different setting (keyAttestationConfig). Credential c_nonce values from /openid4vci/nonce are a third setting. None of the three replaces the others.

This documents walt-id/waltid-identity#2421 (WAL-1587).

Enable challenges

Add challenge on the client-attestation method in clientAuthenticationConfig.supportedMethods, inside issuer-service.conf, and restart Issuer2:

clientAuthenticationConfig {
  supportedMethods = [
    {
      type = "client-attestation"
      config { ... }
      challenge {
        lifetimeSeconds = 300
      }
    }
  ]
}
ValueEffect
challenge omitted, or challenge = nullNo challenge endpoint, no challenge_endpoint in discovery, and no challenge check
challenge = {}Challenges on, with a 300-second lifetime
lifetimeSecondsA positive number of seconds. The signed challenge is bound to this issuer and signed with the token signing key

Authorization server metadata, including signed_metadata when that is configured, advertises challenge_endpoint only while challenges are enabled.

Fetch a challenge

The endpoint takes no authentication and no body. It returns 404 while challenges are disabled.

curl -i -X POST http://localhost:7005/openid4vci/challenge
HTTP/1.1 200 OK
Cache-Control: no-store

{"attestation_challenge":"<signed-challenge>"}

Put that value in the challenge claim of the client-attestation proof-of-possession JWT (OAuth-Client-Attestation-PoP). The same challenge can be reused at PAR, the token endpoint, and refresh until it expires. Each request still needs a newly signed proof of possession with a fresh jti.

Retry when a challenge is missing or expired

PAR and the token endpoint answer HTTP 400 before they consume the grant:

OAuth-Client-Attestation-Challenge: <fresh-signed-challenge>

{"error":"use_attestation_challenge"}

Retry POST /openid4vci/par with the same authorization parameters and a new proof of possession whose challenge is that header. Retry POST /openid4vci/token the same way, keeping the authorization code, pre-authorized code, or refresh token. Browsers can read the header because it is exposed through CORS.

Other attestation failures keep their usual OAuth errors. A pre-authorized request that is explicitly allowed to be anonymous stays allowed.

Last updated on November 4, 2026