apereo/cas · error · IllegalArgumentException

Proof nonce is invalid or missing

Error message

Proof nonce %s is invalid or missing

What it means

Thrown by OidcVerifiableCredentialJwtProofValidator.verifyNonce when the proof JWT presented during a credential request has no 'nonce' claim, or the nonce it carries does not exist in the OidcVerifiableCredentialNonceService store (issued challenge not found / already consumed / expired). Nonces bind the proof to a specific credential offer to prevent replay.

Solutions

  1. Request a fresh c_nonce from the credential endpoint and include it as the 'nonce' claim in the proof JWT before submitting.
  2. Never reuse a nonce; fetch a new one for each credential request.
  3. Configure a shared/persistent nonce store (e.g. ticket-registry-backed) when running multiple CAS nodes.
  4. Ensure the proof is submitted promptly, before the nonce expires or is evicted.

Example fix

// before
JWTClaimsSet claims = new JWTClaimsSet.Builder().issuer(holderId).build(); // no nonce
// after
JWTClaimsSet claims = new JWTClaimsSet.Builder().issuer(holderId).claim("nonce", cNonce).build();
Defensive patterns

Strategy: validation

Validate before calling

String nonce = claims.getStringClaim("nonce");
if (nonce == null || !nonceService.exists(nonce)) {
    throw new IllegalStateException("Obtain a fresh c_nonce before building the proof");
}

Try / catch

try {
    validator.validate(signedJwt, holderJwk);
} catch (IllegalArgumentException e) {
    // fetch a new c_nonce and rebuild the proof
}

Prevention

When it happens

Trigger: Submitting a credential request whose proof JWT omits the nonce claim; using a nonce that was never issued by the credential endpoint; reusing a nonce that was deleted after first use; nonce store (in-memory) lost due to server restart or multi-node deployment without shared storage.

Common situations: Clients skip the c_nonce round trip and build proofs without requesting a challenge first; load-balanced CAS nodes each with their own in-memory nonce service; nonce reused from a previous credential request; clock/store expiry before the proof is submitted.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08). Data as JSON: /api/errors/c8bc310d0d3af1b6. Report an issue: GitHub.

Appendix: source

Thrown at support/cas-server-support-oidc-vc/src/main/java/org/apereo/cas/oidc/vc/issuer/proof/OidcVerifiableCredentialJwtProofValidator.java:60

        verifyAudience(signedJwt);
        verifyFreshness(signedJwt);
        val nonce = verifyNonce(signedJwt);

        val claims = signedJwt.getJWTClaimsSet();
        return new VerifiableCredentialProofResult(
            "jwt",
            claims.getJWTID(),
            claims.getSubject(),
            holderJwk,
            nonce
        );
    }

    protected @Nullable String verifyNonce(final SignedJWT signedJwt) throws Exception {
        val claims = signedJwt.getJWTClaimsSet();
        val nonce = claims.getStringClaim("nonce");
        if (nonce == null || !oidcVerifiableCredentialNonceService.exists(nonce)) {
            throw new IllegalArgumentException("Proof nonce %s is invalid or missing".formatted(nonce));
        }
        return nonce;
    }

    protected void verifySignature(final SignedJWT signedJwt, final JWK holderJwk) throws Exception {
        JWSVerifier verifier = null;
        if (holderJwk instanceof final RSAKey rsaKey) {
            verifier = new RSASSAVerifier(rsaKey);
        } else if (holderJwk instanceof final ECKey ecKey) {
            verifier = new ECDSAVerifier(ecKey);
        }
        if (verifier == null || !signedJwt.verify(verifier)) {
            throw new IllegalArgumentException("Proof JWT signature validation failed");
        }
    }

    protected void verifyAudience(final SignedJWT signedJwt) throws ParseException {
        val audiences = signedJwt.getJWTClaimsSet().getAudience();

View on GitHub (pinned to e7288fc434)