apereo/cas · error · CredentialsException

Code verification method is unrecognized:

Error message

Code verification method is unrecognized: 

What it means

During PKCE (Proof Key for Code Exchange) validation, OAuth20ProofKeyCodeExchangeAuthenticator converts the client's code_verifier into a hash using the code_challenge_method stored on the OAuth code ticket. Only 'plain' and 'S256' are supported (RFC 7636); any other method value causes a CredentialsException.

Solutions

  1. Fix the client to use a supported PKCE method: send code_challenge_method=S256 (preferred) or omit it for plain
  2. Re-issue the authorization code — old tickets hold the bad method; a fresh code/verifier pair will work
  3. If a client library hardcodes an unsupported method (e.g. S512), switch libraries or upgrade it to RFC 7636 compliance
  4. Check ticket registry contents if tickets are shared/manipulated by external tooling

Example fix

// client before (authorization request)
&code_challenge=abc&code_challenge_method=S512

// after
&code_challenge=base64url(sha256(verifier))&code_challenge_method=S256
Defensive patterns

Strategy: validation

Validate before calling

// client-side preflight (JS) before building the authorization request
const method = "S256"; // only RFC 7636 'plain' and 'S256' are supported
if (method !== "S256" && method !== "plain") {
  throw new Error("Unsupported PKCE method: " + method);
}

Type guard

boolean isSupportedPkceMethod(String method) {
    return "S256".equalsIgnoreCase(method) || "plain".equalsIgnoreCase(method) || StringUtils.isBlank(method);
}

Try / catch

try {
    authenticator.validate(credentials, context);
} catch (CredentialsException e) {
    logger.warn("PKCE validation failed: {}", e.getMessage());
    throw e;
}

Prevention

When it happens

Trigger: calculateCodeVerifierHash (called from hash) receives a method string that is neither 'plain' (default/empty) nor 'S256' — typically because the OAuth code ticket persisted a codeChallengeMethod with an unexpected value.

Common situations: A client/library sending a nonstandard code_challenge_method (e.g. 'S512') at authorization time; legacy tickets persisted by an older CAS version with an unknown method value; manually forged or corrupted ticket payloads; case variants are handled (equalsIgnoreCase), so this is almost always a genuinely different method string.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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

        final OAuth20RequestParameterResolver requestParameterResolver,
        final OAuth20ClientSecretValidator clientSecretValidator,
        final OAuth20ProfileScopeToAttributesFilter profileScopeToAttributesFilter,
        final TicketFactory ticketFactory,
        final ConfigurableApplicationContext applicationContext) {
        super(servicesManager, webApplicationServiceFactory, registeredServiceAccessStrategyEnforcer,
            ticketRegistry, principalResolver, requestParameterResolver, clientSecretValidator,
            profileScopeToAttributesFilter, ticketFactory, applicationContext);
    }

    private static String calculateCodeVerifierHash(final String method, final String codeVerifier) {
        if ("plain".equalsIgnoreCase(method)) {
            return codeVerifier;
        }
        if ("S256".equalsIgnoreCase(method)) {
            val sha256 = DigestUtils.rawDigestSha256(codeVerifier);
            return EncodingUtils.encodeUrlSafeBase64(sha256);
        }
        throw new CredentialsException("Code verification method is unrecognized: " + method);
    }

    @Override
    protected boolean canAuthenticate(final CallContext callContext) {
        val context = callContext.webContext();
        return getRequestParameterResolver().resolveRequestParameter(context, OAuth20Constants.CODE_VERIFIER).isPresent()
            && getRequestParameterResolver().resolveRequestParameter(context, OAuth20Constants.CODE).isPresent();
    }

    @Override
    protected void validateCredentials(final UsernamePasswordCredentials credentials,
                                       final OAuthRegisteredService registeredService,
                                       final CallContext callContext,
                                       final OAuth20ClientAuthenticationMethods authnMethod) {
        val clientSecret = getRequestParameterResolver().resolveClientIdAndClientSecret(callContext).getRight();
        if (!getClientSecretValidator().validate(registeredService, clientSecret)) {
            throw new CredentialsException("Client Credentials provided is not valid for service: " + registeredService.getName());
        }

View on GitHub (pinned to e7288fc434)