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
- Request a fresh c_nonce from the credential endpoint and include it as the 'nonce' claim in the proof JWT before submitting.
- Never reuse a nonce; fetch a new one for each credential request.
- Configure a shared/persistent nonce store (e.g. ticket-registry-backed) when running multiple CAS nodes.
- 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
- Always request a c_nonce immediately before building the proof and use it exactly once.
- Use a shared nonce store across all CAS nodes.
- Never cache or replay nonces across requests.
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
- Proof JWT signature validation failed
- Proof audience does not match credential issuer
- Proof JWT algorithm is invalid
- Proof JWT is missing iat
- invalid_request
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)