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
- Fix the client to use a supported PKCE method: send code_challenge_method=S256 (preferred) or omit it for plain
- Re-issue the authorization code — old tickets hold the bad method; a fresh code/verifier pair will work
- If a client library hardcodes an unsupported method (e.g. S512), switch libraries or upgrade it to RFC 7636 compliance
- 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
- Clients should always send code_challenge_method=S256 with a correct BASE64URL(SHA256(verifier)) challenge
- Never invent custom methods like S512; RFC 7636 defines only plain and S256
- Re-run the whole authorization flow (new code) after changing PKCE parameters
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
- Client Credentials provided is not valid for service:
- Invalid token:
- Code verification does not match the challenge assigned to:
- No client credentials could be identified in this request
- Ignoring malformed request
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)