apereo/cas · error · IllegalArgumentException

Proof audience does not match credential issuer

Error message

Proof audience does not match credential issuer

What it means

Thrown by OidcVerifiableCredentialJwtProofValidator.verifyAudience when the proof JWT's 'aud' claim is missing or does not contain the configured CAS OIDC credential issuer URL (casProperties.getAuthn().getOidc().getCore().getIssuer()). Per OIDC4VCI, the proof must be audience-restricted to the credential issuer to prevent proof reuse against other issuers.

Solutions

  1. Set the proof JWT's aud claim to exactly the CAS OIDC issuer identifier (cas.authn.oidc.core.issuer).
  2. Compare the aud value character-by-character with the configured issuer, including scheme, host, and trailing slash.
  3. Update client proof code after any change to the CAS issuer configuration or public base URL.
  4. Check casProperties.getAuthn().getOidc().getCore().getIssuer() (via the discovery document 'issuer') to learn the expected value.

Example fix

// before
.claim("aud", "https://cas.example.org/oidc/token")
// after
.claim("aud", "https://cas.example.org/oidc") // must equal the credential issuer
Defensive patterns

Strategy: validation

Validate before calling

List<String> aud = claims.getAudience();
if (aud == null || !aud.contains(credentialIssuer)) {
    throw new IllegalArgumentException("aud must equal the credential issuer");
}

Try / catch

try {
    validator.validate(signedJwt, holderJwk);
} catch (IllegalArgumentException e) {
    // rebuild proof with aud = issuer from the discovery document
}

Prevention

When it happens

Trigger: Proof JWT built without an aud claim; aud set to a different value (token endpoint, web origin, wrong environment URL) instead of the issuer identifier; CAS issuer property changed (e.g. moved from http to https or a new host) while clients still target the old value.

Common situations: Developers copy proof-building code that sets aud to the token endpoint (as OIDC4VCI pre-draft or other specs require); issuer base URL mismatch between environments (dev vs prod); trailing-slash differences between the configured issuer and the aud value.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08). Data as JSON: /api/errors/0df5709d6c2c18dc. 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:81

    }

    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();
        val credentialIssuer = casProperties.getAuthn().getOidc().getCore().getIssuer();
        if (audiences == null || !audiences.contains(credentialIssuer)) {
            throw new IllegalArgumentException("Proof audience does not match credential issuer");
        }
    }

    protected void verifyAlgorithm(final SignedJWT signedJwt, final JWK holderJwk) {
        val alg = signedJwt.getHeader().getAlgorithm();
        if (alg == null || Algorithm.NONE.equals(alg)) {
            throw new IllegalArgumentException("Proof JWT algorithm is invalid");
        }
        if (holderJwk instanceof RSAKey && !JWSAlgorithm.Family.RSA.contains(alg)) {
            throw new IllegalArgumentException("Proof JWT algorithm does not match RSA holder key");
        }
        if (holderJwk instanceof ECKey && !JWSAlgorithm.Family.EC.contains(alg)) {
            throw new IllegalArgumentException("Proof JWT algorithm does not match EC holder key");
        }
    }

    protected void verifyFreshness(final SignedJWT signedJwt) throws ParseException {
        val claims = signedJwt.getJWTClaimsSet();

View on GitHub (pinned to e7288fc434)