{"record":{"id":"bcd1f79272984415","repo":"apereo/cas","slug":"code-verification-method-is-unrecognized","errorCode":null,"errorMessage":"Code verification method is unrecognized: ","messagePattern":"Code verification method is unrecognized: ","errorType":"exception","errorClass":"CredentialsException","httpStatus":null,"severity":"error","filePath":"support/cas-server-support-oauth-core-api/src/main/java/org/apereo/cas/support/oauth/authenticator/OAuth20ProofKeyCodeExchangeAuthenticator.java","lineNumber":60,"sourceCode":"        final OAuth20RequestParameterResolver requestParameterResolver,\n        final OAuth20ClientSecretValidator clientSecretValidator,\n        final OAuth20ProfileScopeToAttributesFilter profileScopeToAttributesFilter,\n        final TicketFactory ticketFactory,\n        final ConfigurableApplicationContext applicationContext) {\n        super(servicesManager, webApplicationServiceFactory, registeredServiceAccessStrategyEnforcer,\n            ticketRegistry, principalResolver, requestParameterResolver, clientSecretValidator,\n            profileScopeToAttributesFilter, ticketFactory, applicationContext);\n    }\n\n    private static String calculateCodeVerifierHash(final String method, final String codeVerifier) {\n        if (\"plain\".equalsIgnoreCase(method)) {\n            return codeVerifier;\n        }\n        if (\"S256\".equalsIgnoreCase(method)) {\n            val sha256 = DigestUtils.rawDigestSha256(codeVerifier);\n            return EncodingUtils.encodeUrlSafeBase64(sha256);\n        }\n        throw new CredentialsException(\"Code verification method is unrecognized: \" + method);\n    }\n\n    @Override\n    protected boolean canAuthenticate(final CallContext callContext) {\n        val context = callContext.webContext();\n        return getRequestParameterResolver().resolveRequestParameter(context, OAuth20Constants.CODE_VERIFIER).isPresent()\n            && getRequestParameterResolver().resolveRequestParameter(context, OAuth20Constants.CODE).isPresent();\n    }\n\n    @Override\n    protected void validateCredentials(final UsernamePasswordCredentials credentials,\n                                       final OAuthRegisteredService registeredService,\n                                       final CallContext callContext,\n                                       final OAuth20ClientAuthenticationMethods authnMethod) {\n        val clientSecret = getRequestParameterResolver().resolveClientIdAndClientSecret(callContext).getRight();\n        if (!getClientSecretValidator().validate(registeredService, clientSecret)) {\n            throw new CredentialsException(\"Client Credentials provided is not valid for service: \" + registeredService.getName());\n        }","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-oauth-core-api/src/main/java/org/apereo/cas/support/oauth/authenticator/OAuth20ProofKeyCodeExchangeAuthenticator.java#L42-L78","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// client before (authorization request)\n&code_challenge=abc&code_challenge_method=S512\n\n// after\n&code_challenge=base64url(sha256(verifier))&code_challenge_method=S256","handlingStrategy":"validation","validationCode":"// client-side preflight (JS) before building the authorization request\nconst method = \"S256\"; // only RFC 7636 'plain' and 'S256' are supported\nif (method !== \"S256\" && method !== \"plain\") {\n  throw new Error(\"Unsupported PKCE method: \" + method);\n}","typeGuard":"boolean isSupportedPkceMethod(String method) {\n    return \"S256\".equalsIgnoreCase(method) || \"plain\".equalsIgnoreCase(method) || StringUtils.isBlank(method);\n}","tryCatchPattern":"try {\n    authenticator.validate(credentials, context);\n} catch (CredentialsException e) {\n    logger.warn(\"PKCE validation failed: {}\", e.getMessage());\n    throw e;\n}","preventionTips":["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"],"tags":["oauth2","pkce","validation"],"backgroundTag":"invalid-enum-value","analyzedSha":"e7288fc434b4f4505b8452e1a57e8fb3111bb863","analyzedAt":"2026-09-08T15:39:16.015Z","contentChangedAt":"2026-09-08T15:39:16.015Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}