apereo/cas · error
invalid_client
invalid_client
Error message
Unable to locate and extract credentials from the request
What it means
The OAuth 2.0 introspection endpoint could not extract client credentials (client id / secret) from the HTTP POST request. CAS looks for Basic Auth headers or client_id/client_secret form parameters via extractCredentials(); when neither is present, it rejects the call with an OAuth 'invalid_client' error and HTTP 401.
Solutions
- Send the client id and secret as HTTP Basic Authorization on the introspection POST request
- Alternatively POST client_id and client_secret as application/x-www-form-urlencoded body parameters
- Verify no proxy or gateway strips the Authorization header
- Ensure the Content-Type is application/x-www-form-urlencoded so parameters are parsed
Example fix
// before curl -X POST https://cas.example.org/cas/oauth2.0/introspect -d 'token=AT-123' // after curl -u myClient:mySecret -X POST https://cas.example.org/cas/oauth2.0/introspect -d 'token=AT-123'
Defensive patterns
Strategy: validation
Validate before calling
// client-side pre-check before calling the endpoint
const hasBasic = config.clientId && config.clientSecret;
const hasForm = !!config.formParams?.client_id;
if (!hasBasic && !hasForm) {
throw new Error('Introspection requires Basic auth or client_id/client_secret form params');
} Try / catch
try {
const res = await fetch(introspectUrl, { method: 'POST', headers: { Authorization: 'Basic ' + btoa(id + ':' + secret) } });
if (res.status === 401 && (await res.text()).includes('invalid_client')) {
// credentials were missing/unparseable: fix auth header before retrying
}
} catch (e) { /* network failure */ } Prevention
- Always send HTTP Basic auth on the introspection request
- Use application/x-www-form-urlencoded content type
- Check reverse proxy config for Authorization header stripping
- Log the outgoing headers (redacted) when debugging 401s
When it happens
Trigger: Calling POST to the introspection endpoint without an Authorization: Basic header and without client_id/client_secret form parameters, or with credentials that extractCredentials() cannot parse (e.g. malformed Basic base64, wrong Content-Type so form params are not read).
Common situations: Client apps omitting the Basic auth header after a framework upgrade; sending credentials as a JSON body instead of form-encoded parameters; reverse proxies stripping the Authorization header; typo in the parameter names.
Related errors
- Invalid client credentials provided for registered service:
- Client Credentials provided is not valid for service:
- missing_access_token
- 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/e5fbdf300afcab0a.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-oauth-core-api/src/main/java/org/apereo/cas/support/oauth/web/endpoints/OAuth20IntrospectionEndpointController.java:99
}
/**
* Handle post request.
*
* @param request the request
* @param response the response
* @return the response entity
* @throws Throwable the throwable
*/
@PostMapping(value = OAuth20Constants.BASE_OAUTH20_URL + '/' + OAuth20Constants.INTROSPECTION_URL,
produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Handle OAuth introspection request")
public ResponseEntity handlePostRequest(final HttpServletRequest request, final HttpServletResponse response) throws Throwable {
try {
val context = new JEEContext(request, response);
val credentialsResult = extractCredentials(context);
if (credentialsResult.isEmpty()) {
LOGGER.warn("Unable to locate and extract credentials from the request");
return buildUnauthorizedResponseEntity(OAuth20Constants.INVALID_CLIENT, true);
}
val credentials = (UsernamePasswordCredentials) credentialsResult.get();
val registeredService = OAuth20Utils.getRegisteredOAuthServiceByClientId(
getConfigurationContext().getServicesManager(), credentials.getUsername());
if (registeredService == null) {
LOGGER.warn("Unable to locate service definition by client id [{}]", credentials.getUsername());
return buildUnauthorizedResponseEntity(OAuth20Constants.INVALID_CLIENT, true);
}
val validationError = validateIntrospectionRequest(registeredService, credentials, request);
if (validationError.isPresent()) {
return validationError.get();
}
val tokenId = StringUtils.defaultIfBlank(request.getParameter(OAuth20Constants.TOKEN),
request.getParameter(OAuth20Constants.ACCESS_TOKEN));View on GitHub (pinned to e7288fc434)