{"record":{"id":"653d7f85bd51473f","repo":"apereo/cas","slug":"invalid-client-credentials-provided-for-registered","errorCode":null,"errorMessage":"Invalid client credentials provided for registered service: ","messagePattern":"Invalid client credentials provided for registered 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/OAuth20ClientIdClientSecretAuthenticator.java","lineNumber":181,"sourceCode":"        val finalPrincipal = profileScopeToAttributesFilter.filter(service, resolvedPrincipal, registeredService, accessToken);\n        LOGGER.debug(\"Built final principal [{}]\", finalPrincipal);\n        return finalPrincipal;\n    }\n\n    protected Collection<String> resolveRequestedScopes(final CallContext callContext) {\n        return requestParameterResolver.resolveRequestedScopes(callContext.webContext());\n    }\n\n    protected void validateCredentials(final UsernamePasswordCredentials credentials,\n                                       final OAuthRegisteredService registeredService,\n                                       final CallContext callContext,\n                                       final OAuth20ClientAuthenticationMethods authnMethod) {\n        var pwdToCheck = credentials.getPassword();\n        if (authnMethod == OAuth20ClientAuthenticationMethods.CLIENT_SECRET_BASIC) {\n            pwdToCheck = EncodingUtils.urlDecode(credentials.getPassword());\n        }\n        if (!clientSecretValidator.validate(registeredService, pwdToCheck)) {\n            throw new CredentialsException(\"Invalid client credentials provided for registered service: \" + registeredService.getName());\n        }\n    }\n\n    protected boolean canAuthenticate(final CallContext callContext) {\n        val context = callContext.webContext();\n        val grantType = requestParameterResolver.resolveGrantType(context);\n\n        if (grantType == OAuth20GrantTypes.PASSWORD) {\n            LOGGER.debug(\"Skipping client credential authentication to use password authentication\");\n            return false;\n        }\n\n        val clientIdAndSecret = requestParameterResolver.resolveClientIdAndClientSecret(callContext);\n        if (grantType == OAuth20GrantTypes.REFRESH_TOKEN\n            && StringUtils.isNotBlank(clientIdAndSecret.getKey())\n            && StringUtils.isBlank(clientIdAndSecret.getValue())) {\n            LOGGER.debug(\"Skipping client credential authentication to use refresh token authentication\");\n            return false;","sourceCodeStart":163,"sourceCodeEnd":199,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-oauth-core-api/src/main/java/org/apereo/cas/support/oauth/authenticator/OAuth20ClientIdClientSecretAuthenticator.java#L163-L199","documentation":"OAuth20ClientIdClientSecretAuthenticator validates OAuth2 client authentication (basic auth or POST body client_id/client_secret) against the registered service's stored secret via OAuth20ClientSecretValidator. When the presented secret does not match, it throws pac4j CredentialsException naming the registered service, rejecting client authentication.","triggerScenarios":"validateCredentials is invoked by the pac4j validate() flow when a client presents client_id/client_secret; clientSecretValidator.validate(registeredService, pwdToCheck) returns false — the secret is wrong, or (for CLIENT_SECRET_BASIC) the URL-decoded password still doesn't match.","commonSituations":"Client rotated its secret but the CAS service registry still holds the old one; percent-encoded secrets in HTTP Basic auth not matching after decode; whitespace/quote characters pasted into the registered service's clientSecret; JWT-signed vs plain secret mismatch (service configured as jwt-secret but client sends plain).","solutions":["Compare the client's sent secret with the registered service's clientSecret in the service registry (JSON/YAML) and update whichever is stale","If the service uses a signed/JWT secret, ensure the client computes and sends the secret per that method (jose-signed vs plain)","Check for copy/paste artifacts (leading/trailing spaces, quotes) in both the client config and registry entry","Confirm the client is sending credentials correctly: Basic auth header or POST parameters client_id/client_secret, properly URL-encoded"],"exampleFix":"// service registry before\n\"clientId\": \"myapp\", \"clientSecret\": \"old-secret\"\n\n// after  (sync with the client after rotation)\n\"clientId\": \"myapp\", \"clientSecret\": \"new-secret\"","handlingStrategy":"validation","validationCode":"// verify registry secret matches client config before deploy\nOAuthRegisteredService svc = servicesManager.findServiceBy(clientId);\nif (svc != null && !svc.getClientSecret().equals(expectedClientSecret)) {\n    throw new IllegalStateException(\"Client secret mismatch for service \" + svc.getName());\n}","typeGuard":null,"tryCatchPattern":"try {\n    authenticator.validate(credentials, context);\n} catch (CredentialsException e) {\n    logger.warn(\"OAuth client auth rejected: {}\", e.getMessage());\n    throw e; // surfaces as 401 invalid_client\n}","preventionTips":["Rotate client secrets in both the client and the CAS service registry atomically","Avoid copy/paste artifacts (quotes, whitespace) in registry clientSecret values","Know your secret mode (plain vs jose-signed) and configure both sides consistently","Audit service registry entries after client onboarding"],"tags":["oauth2","client-authentication","bad-credentials"],"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"}