{"record":{"id":"4cc374dba880e066","repo":"apereo/cas","slug":"client-credentials-provided-is-not-valid-for-servi","errorCode":null,"errorMessage":"Client Credentials provided is not valid for service: ","messagePattern":"Client Credentials provided is not valid for service: ","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":77,"sourceCode":"        }\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        }\n        val codeVerifier = getRequestParameterResolver()\n            .resolveRequestParameter(callContext.webContext(), OAuth20Constants.CODE_VERIFIER)\n            .map(String::valueOf).orElse(StringUtils.EMPTY);\n        val code = getRequestParameterResolver()\n            .resolveRequestParameter(callContext.webContext(), OAuth20Constants.CODE)\n            .map(String::valueOf).orElse(StringUtils.EMPTY);\n\n        LOGGER.debug(\"Received PKCE code verifier [{}] along with code [{}]\", codeVerifier, code);\n        val token = getTicketRegistry().getTicket(code, OAuth20Code.class);\n        if (token == null || token.isExpired()) {\n            LOGGER.error(\"Provided code [{}] is either not found in the ticket registry or has expired\", code);\n            throw new CredentialsException(\"Invalid token: \" + code);\n        }\n\n        val method = StringUtils.defaultIfEmpty(token.getCodeChallengeMethod(), \"plain\");\n        val hash = calculateCodeVerifierHash(method, codeVerifier);\n        if (!hash.equalsIgnoreCase(token.getCodeChallenge())) {","sourceCodeStart":59,"sourceCodeEnd":95,"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#L59-L95","documentation":"PKCE authenticator variant of client secret validation: before checking the code verifier, validateCredentials resolves the client secret from the request (via resolveClientIdAndClientSecret) and checks it against the registered service. A failed OAuth20ClientSecretValidator check throws this CredentialsException naming the service, rejecting the token request outright.","triggerScenarios":"A token request using a PKCE authorization code whose client_secret (from Basic auth header or POST body, resolved by the request parameter resolver) fails clientSecretValidator.validate for the registered OAuth service.","commonSituations":"Confidential clients forgetting the client_secret on the token exchange; secret rotation mismatch between client and CAS service registry; public clients mistakenly configured with secret validation expectations; Basic auth header with incorrect Base64 or URL encoding.","solutions":["Ensure the client sends client_id and client_secret on the token request (Basic auth or form body) matching the registry entry","Update the registered service's clientSecret to the client's current secret after any rotation","Verify the service's secret-handling configuration (plain vs jose/JWT secret) matches what the client sends","Inspect resolveClientIdAndClientSecret behavior — for PKCE public clients consider enabling PKCE-only validation without client secret"],"exampleFix":"// token request before\nPOST /cas/oauth2.0/token\ngrant_type=authorization_code&code=...&code_verifier=...\n\n// after\nPOST /cas/oauth2.0/token\ngrant_type=authorization_code&code=...&code_verifier=...&client_id=myapp&client_secret=correct-secret","handlingStrategy":"validation","validationCode":"// client preflight: ensure secret is attached to the PKCE token exchange\nif (!tokenRequest.containsParameter(\"client_secret\") && !usesBasicAuth(tokenRequest)) {\n  throw new Error(\"Confidential PKCE clients must send client_secret on the token request\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    authenticator.validate(credentials, context);\n} catch (CredentialsException e) {\n    logger.warn(\"PKCE client credentials rejected: {}\", e.getMessage());\n    throw e; // respond 401 invalid_client\n}","preventionTips":["Keep client_secret in sync between client apps and the CAS service registry","Include the Basic auth header or form client_secret on every token request for confidential clients","Decide explicitly whether the client is public (PKCE-only) or confidential (secret + PKCE) and configure CAS accordingly"],"tags":["oauth2","pkce","client-authentication"],"backgroundTag":"oauth-client-authentication-failed","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"}