{"record":{"id":"e5fbdf300afcab0a","repo":"apereo/cas","slug":"invalid-client","errorCode":"invalid_client","errorMessage":"Unable to locate and extract credentials from the request","messagePattern":"Unable to locate and extract credentials from the request","errorType":"error_code","errorClass":null,"httpStatus":401,"severity":"error","filePath":"support/cas-server-support-oauth-core-api/src/main/java/org/apereo/cas/support/oauth/web/endpoints/OAuth20IntrospectionEndpointController.java","lineNumber":99,"sourceCode":"    }\n\n    /**\n     * Handle post request.\n     *\n     * @param request  the request\n     * @param response the response\n     * @return the response entity\n     * @throws Throwable the throwable\n     */\n    @PostMapping(value = OAuth20Constants.BASE_OAUTH20_URL + '/' + OAuth20Constants.INTROSPECTION_URL,\n        produces = MediaType.APPLICATION_JSON_VALUE)\n    @Operation(summary = \"Handle OAuth introspection request\")\n    public ResponseEntity handlePostRequest(final HttpServletRequest request, final HttpServletResponse response) throws Throwable {\n        try {\n            val context = new JEEContext(request, response);\n            val credentialsResult = extractCredentials(context);\n            if (credentialsResult.isEmpty()) {\n                LOGGER.warn(\"Unable to locate and extract credentials from the request\");\n                return buildUnauthorizedResponseEntity(OAuth20Constants.INVALID_CLIENT, true);\n            }\n\n            val credentials = (UsernamePasswordCredentials) credentialsResult.get();\n            val registeredService = OAuth20Utils.getRegisteredOAuthServiceByClientId(\n                getConfigurationContext().getServicesManager(), credentials.getUsername());\n            if (registeredService == null) {\n                LOGGER.warn(\"Unable to locate service definition by client id [{}]\", credentials.getUsername());\n                return buildUnauthorizedResponseEntity(OAuth20Constants.INVALID_CLIENT, true);\n            }\n\n            val validationError = validateIntrospectionRequest(registeredService, credentials, request);\n            if (validationError.isPresent()) {\n                return validationError.get();\n            }\n\n            val tokenId = StringUtils.defaultIfBlank(request.getParameter(OAuth20Constants.TOKEN),\n                request.getParameter(OAuth20Constants.ACCESS_TOKEN));","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-oauth-core-api/src/main/java/org/apereo/cas/support/oauth/web/endpoints/OAuth20IntrospectionEndpointController.java#L81-L117","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before\ncurl -X POST https://cas.example.org/cas/oauth2.0/introspect -d 'token=AT-123'\n// after\ncurl -u myClient:mySecret -X POST https://cas.example.org/cas/oauth2.0/introspect -d 'token=AT-123'","handlingStrategy":"validation","validationCode":"// client-side pre-check before calling the endpoint\nconst hasBasic = config.clientId && config.clientSecret;\nconst hasForm = !!config.formParams?.client_id;\nif (!hasBasic && !hasForm) {\n  throw new Error('Introspection requires Basic auth or client_id/client_secret form params');\n}","typeGuard":null,"tryCatchPattern":"try {\n  const res = await fetch(introspectUrl, { method: 'POST', headers: { Authorization: 'Basic ' + btoa(id + ':' + secret) } });\n  if (res.status === 401 && (await res.text()).includes('invalid_client')) {\n    // credentials were missing/unparseable: fix auth header before retrying\n  }\n} catch (e) { /* network failure */ }","preventionTips":["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"],"tags":["oauth2","http-401","client-authentication","introspection"],"backgroundTag":"missing-credentials","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"}