apereo/cas · error · IllegalArgumentException

Unable to verify JWT assertion with any of the configured…

Error message

Unable to verify JWT assertion with any of the configured JSON web keys

What it means

OidcAccessTokenJwtBearerGrantRequestExtractor.verifyAssertion throws this when a JWT bearer (RFC 7523) assertion sent to the token endpoint cannot be verified against any of the configured JSON web keys. Each candidate key is tried and the final failure is reported only after all keys are exhausted, so the assertion's signature is untrusted.

Solutions

  1. Ensure the assertion is signed with a private key whose public counterpart is published in the client's JWKS
  2. Set the assertion 'aud' to the CAS issuer or token endpoint URL as configured
  3. Check assertion 'exp'/'nbf' and system clock skew on the client
  4. Force a JWKS refresh / verify the client's jwks_uri serves the current key set
  5. Enable debug logging for OidcAccessTokenJwtBearerGrantRequestExtractor to see the per-key failure reason

Example fix

// before: assertion built with wrong audience
JWTParser.parse(assertion).getJWTClaimsSet().getAudience() == ["https://wrong-aud"]
// after: audience must match CAS issuer/token endpoint
builder.audience("https://cas.example.org/cas/oidc")
Defensive patterns

Strategy: try-catch

Validate before calling

var claims = SignedJWT.parse(assertion).getJWTClaimsSet();
if (!issuer.equals(claims.getIssuer()) || !expectedAud.containsAll(claims.getAudience()) || new Date().after(claims.getExpirationTime())) {
    throw new IllegalStateException("Client assertion will not verify: check issuer, audience, expiry");
}

Try / catch

try { extractor.verifyAssertion(context, assertion); } catch (IllegalArgumentException e) { throw new InvalidClientException("client assertion verification failed: " + e.getMessage()); }

Prevention

When it happens

Trigger: A private_key_jwt / jwt-bearer grant request supplies a client_assertion whose signature, issuer, audience, or expiry fails validation for every JSON web key available to CAS (client JWKS or trust store); each attempt throws and is caught, then this is thrown.

Common situations: Client signs assertion with a key not present in its JWKS; assertion 'aud' doesn't match the token endpoint/issuer; clock skew makes assertion expired; client rotated keys but CAS cached an old JWKS.

Related errors


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

Appendix: source

Thrown at support/cas-server-support-oidc-core-api/src/main/java/org/apereo/cas/oidc/token/OidcAccessTokenJwtBearerGrantRequestExtractor.java:152

            Set.of(OidcConstants.ISS, OidcConstants.AUD, OAuth20Constants.CLAIM_SUB, OAuth20Constants.CLAIM_EXP),
            Set.of());
        jwtClaimsSetVerifier.verify(JWTClaimsSet.parse(claims.getClaimsMap()), new SimpleSecurityContext());
        return claims;
    }

    protected String verifyAssertion(final String assertion, final List<PublicJsonWebKey> jsonWebKeys) {
        for (val jsonWebKey : jsonWebKeys) {
            try {
                val verified = EncodingUtils.verifyJwsSignature(jsonWebKey.getPublicKey(), assertion);
                val verifiedAssertion = new String(verified, StandardCharsets.UTF_8);
                LOGGER.trace("Successfully verified JWT assertion with key id [{}]", jsonWebKey.getKeyId());
                return verifiedAssertion;
            } catch (final Exception e) {
                LOGGER.debug("Failed to verify JWT assertion via key id [{}]: [{}]. Moving on to the next key",
                    jsonWebKey.getKeyId(), e.getMessage());
            }
        }
        throw new IllegalArgumentException("Unable to verify JWT assertion with any of the configured JSON web keys");
    }

    protected AccessTokenRequestContext extractInternal(
        final WebContext context,
        final AccessTokenRequestContext tokenRequestContext) {
        return tokenRequestContext;
    }

    protected static boolean isAllowedToGenerateRefreshToken() {
        return true;
    }

    @Override
    public boolean supports(final WebContext context) {
        val grantType = getConfigurationContext().getObject().getRequestParameterResolver()
            .resolveRequestParameter(context, OAuth20Constants.GRANT_TYPE).orElse(StringUtils.EMPTY);
        val assertion = getConfigurationContext().getObject().getRequestParameterResolver()
            .resolveRequestParameter(context, OAuth20Constants.ASSERTION).orElse(StringUtils.EMPTY);

View on GitHub (pinned to e7288fc434)