apereo/cas · error · CredentialsException
Invalid client credentials provided for registered service:
Error message
Invalid client credentials provided for registered service:
What it means
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.
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
Example fix
// service registry before "clientId": "myapp", "clientSecret": "old-secret" // after (sync with the client after rotation) "clientId": "myapp", "clientSecret": "new-secret"
Defensive patterns
Strategy: validation
Validate before calling
// verify registry secret matches client config before deploy
OAuthRegisteredService svc = servicesManager.findServiceBy(clientId);
if (svc != null && !svc.getClientSecret().equals(expectedClientSecret)) {
throw new IllegalStateException("Client secret mismatch for service " + svc.getName());
} Try / catch
try {
authenticator.validate(credentials, context);
} catch (CredentialsException e) {
logger.warn("OAuth client auth rejected: {}", e.getMessage());
throw e; // surfaces as 401 invalid_client
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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).
Related errors
- Client Credentials provided is not valid for service:
- invalid_client
- Could not authenticate provided credentials
- Failed to acquire access token
- Code verification method is unrecognized:
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/653d7f85bd51473f.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-oauth-core-api/src/main/java/org/apereo/cas/support/oauth/authenticator/OAuth20ClientIdClientSecretAuthenticator.java:181
val finalPrincipal = profileScopeToAttributesFilter.filter(service, resolvedPrincipal, registeredService, accessToken);
LOGGER.debug("Built final principal [{}]", finalPrincipal);
return finalPrincipal;
}
protected Collection<String> resolveRequestedScopes(final CallContext callContext) {
return requestParameterResolver.resolveRequestedScopes(callContext.webContext());
}
protected void validateCredentials(final UsernamePasswordCredentials credentials,
final OAuthRegisteredService registeredService,
final CallContext callContext,
final OAuth20ClientAuthenticationMethods authnMethod) {
var pwdToCheck = credentials.getPassword();
if (authnMethod == OAuth20ClientAuthenticationMethods.CLIENT_SECRET_BASIC) {
pwdToCheck = EncodingUtils.urlDecode(credentials.getPassword());
}
if (!clientSecretValidator.validate(registeredService, pwdToCheck)) {
throw new CredentialsException("Invalid client credentials provided for registered service: " + registeredService.getName());
}
}
protected boolean canAuthenticate(final CallContext callContext) {
val context = callContext.webContext();
val grantType = requestParameterResolver.resolveGrantType(context);
if (grantType == OAuth20GrantTypes.PASSWORD) {
LOGGER.debug("Skipping client credential authentication to use password authentication");
return false;
}
val clientIdAndSecret = requestParameterResolver.resolveClientIdAndClientSecret(callContext);
if (grantType == OAuth20GrantTypes.REFRESH_TOKEN
&& StringUtils.isNotBlank(clientIdAndSecret.getKey())
&& StringUtils.isBlank(clientIdAndSecret.getValue())) {
LOGGER.debug("Skipping client credential authentication to use refresh token authentication");
return false;View on GitHub (pinned to e7288fc434)