apereo/cas · error · CredentialsException

Code verification does not match the challenge assigned to:

Error message

Code verification does not match the challenge assigned to: 

What it means

This CredentialsException is thrown by the OAuth 2.0 PKCE authenticator when the client-supplied code_verifier, after hashing with the challenge method stored on the token (plain or S256), does not equal the code_challenge bound to the authorization code/token. It protects against authorization-code interception: only the client that ran the original PKCE challenge can redeem the code.

Solutions

  1. Regenerate the code_verifier and restart the PKCE flow so the code_challenge sent to /authorize is derived (S256: BASE64URL(SHA256(verifier))) from that exact verifier
  2. Verify the code_challenge_method stored on the token matches the client's hashing implementation and that the digest is base64url-encoded without padding
  3. Ensure the same verifier string (no trimming, no re-encoding) is sent in the token request that produced the challenge
  4. Check for proxies/gateways altering query/body parameters (e.g. plus-sign handling corrupting base64url values)

Example fix

// before
codeChallenge = Base64.getEncoder().encodeToString(digest);
// after
codeChallenge = Base64.getUrlEncoder().withoutPadding().encodeToString(digest);
Defensive patterns

Strategy: validation

Validate before calling

const digest = crypto.createHash('sha256').update(codeVerifier).digest('base64url');
if (digest !== storedCodeChallenge) throw new Error('PKCE verifier does not match challenge before sending');

Try / catch

try {
  await requestToken({ code, codeVerifier });
} catch (e) {
  if (String(e.message).includes('Code verification does not match')) {
    // restart full PKCE flow: new verifier + challenge
  }
}

Prevention

When it happens

Trigger: Redeeming an authorization code (or token request requiring PKCE) where the code_verifier parameter is missing, was generated for a different authorization request, the code_challenge_method used at authorize time differs from the hashing applied now (e.g. server expects S256 but client sent a plain verifier), or the challenge/verifier pair was corrupted in transit.

Common situations: Client library misconfiguration switching between plain and S256; regenerating the verifier after the authorize redirect; custom mobile/SPA clients hashing the verifier with the wrong algorithm (e.g. base64 vs base64url, including padding in the S256 digest); replaying an old code_verifier with a new code.

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/31617c2dc8e2e182. Report an issue: GitHub.

Appendix: source

Thrown at support/cas-server-support-oauth-core-api/src/main/java/org/apereo/cas/support/oauth/authenticator/OAuth20ProofKeyCodeExchangeAuthenticator.java:97

        val codeVerifier = getRequestParameterResolver()
            .resolveRequestParameter(callContext.webContext(), OAuth20Constants.CODE_VERIFIER)
            .map(String::valueOf).orElse(StringUtils.EMPTY);
        val code = getRequestParameterResolver()
            .resolveRequestParameter(callContext.webContext(), OAuth20Constants.CODE)
            .map(String::valueOf).orElse(StringUtils.EMPTY);

        LOGGER.debug("Received PKCE code verifier [{}] along with code [{}]", codeVerifier, code);
        val token = getTicketRegistry().getTicket(code, OAuth20Code.class);
        if (token == null || token.isExpired()) {
            LOGGER.error("Provided code [{}] is either not found in the ticket registry or has expired", code);
            throw new CredentialsException("Invalid token: " + code);
        }

        val method = StringUtils.defaultIfEmpty(token.getCodeChallengeMethod(), "plain");
        val hash = calculateCodeVerifierHash(method, codeVerifier);
        if (!hash.equalsIgnoreCase(token.getCodeChallenge())) {
            LOGGER.error("Code verifier [{}] does not match the challenge [{}]", hash, token.getCodeChallenge());
            throw new CredentialsException("Code verification does not match the challenge assigned to: " + token.getId());
        }
        LOGGER.debug("Validated code verifier using verification method [{}]", method);
    }
}

View on GitHub (pinned to e7288fc434)