{"record":{"id":"0715868dbbf4dc6b","repo":"apereo/cas","slug":"unable-to-verify-credentials","errorCode":null,"errorMessage":"Unable to verify credentials","messagePattern":"Unable to verify credentials","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"support/cas-server-support-oidc-core-api/src/main/java/org/apereo/cas/oidc/authn/OidcJwtAuthenticator.java","lineNumber":126,"sourceCode":"            RegisteredServiceAccessStrategyUtils.ensureServiceAccessIsAllowed(registeredService);\n            Objects.requireNonNull(registeredService, \"regisetered service is null\");\n\n            if (OAuth20Utils.isAccessTokenRequest(callContext.webContext())) {\n                val authMethodDisabled = oidcServerDiscoverySettings.getTokenEndpointAuthMethodsSupported()\n                    .stream()\n                    .map(OAuth20ClientAuthenticationMethods::parse)\n                    .noneMatch(method -> method == OAuth20ClientAuthenticationMethods.CLIENT_SECRET_JWT || method == OAuth20ClientAuthenticationMethods.PRIVATE_KEY_JWT);\n                if (authMethodDisabled || !OAuth20Utils.isTokenAuthenticationMethodSupportedFor(callContext, registeredService,\n                    OAuth20ClientAuthenticationMethods.CLIENT_SECRET_JWT, OAuth20ClientAuthenticationMethods.PRIVATE_KEY_JWT)) {\n                    LOGGER.warn(\"Private key JWT authentication method is not enabled for CAS, or is not supported for service [{}]\", registeredService.getName());\n                    return Optional.<Credentials>empty();\n                }\n            }\n\n            val credentials = (UsernamePasswordCredentials) creds;\n            val jwt = verifyCredentials(credentials, callContext.webContext());\n            if (jwt == null) {\n                LOGGER.warn(\"Unable to verify credentials\");\n                return Optional.<Credentials>empty();\n            }\n\n            val keys = new JsonWebKeySet();\n            clientJwksRegistrationStore.ifAvailable(Unchecked.consumer(store -> {\n                if (jwt instanceof final SignedJWT signedJWT) {\n                    val jwk = signedJWT.getHeader().getJWK();\n                    val kid = signedJWT.getHeader().getKeyID();\n                    val jkt = jwk != null ? jwk.computeThumbprint().toString() : StringUtils.EMPTY;\n                    store.findBy(registeredService.getClientId(), jkt)\n                        .or(() -> store.findBy(registeredService.getClientId(), kid))\n                        .map(ClientJwksRegistrationEntry::jwk)\n                        .ifPresent(registereredKey -> {\n                            val webKey = EncodingUtils.newJsonWebKey(registereredKey);\n                            keys.addJsonWebKey(webKey);\n                        });\n                }\n            }));","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-oidc-core-api/src/main/java/org/apereo/cas/oidc/authn/OidcJwtAuthenticator.java#L108-L144","documentation":"After passing the enablement checks, OidcJwtAuthenticator calls verifyCredentials to validate the client's client_assertion JWT (signature, issuer, expiry, audience). If verification fails and returns null, the authenticator cannot produce Credentials and returns empty, logging this warning.","triggerScenarios":"A private_key_jwt / client_secret_jwt assertion posted to the OIDC token endpoint fails verification in verifyCredentials — e.g. JWT signed with a key not present in the client's registered JWKS, wrong issuer/subject/client_id, expired assertion, or alg not accepted.","commonSituations":"Client's JWKS rotated or key IDs mismatched; clock skew making the assertion expired/not-yet-valid; assertion `iss`/`sub` not matching the authenticated client_id; client_secret_jwt HMAC secret mismatch after a secret change; malformed assertion produced by the client SDK.","solutions":["Inspect verifyCredentials failure details (enable debug logging) — typically signature verification against the registered JWKS failed.","Verify the client's registered jwks URI/content matches the private key actually used to sign the assertion (correct kid).","Ensure assertion claims are correct: iss and sub = client_id, aud = token endpoint/issuer URL, exp/iat within allowed clock skew.","Regenerate the client assertion with a supported algorithm and current timestamp; sync client_secret if using client_secret_jwt."],"exampleFix":"// before\nconst assertion = await new SignJWT({ }).setIssuer('wrong-client').setAudience('https://cas/oidc')...\n// after\nconst assertion = await new SignJWT({ }).setIssuer('myclient').setSubject('myclient')\n  .setAudience('https://cas/cas/oidc/accessToken').setIssuedAt().setExpirationTime('now + 5m')\n  .setProtectedHeader({ alg: 'RS256', kid: 'key1' }).sign(privateKey);","handlingStrategy":"validation","validationCode":"// validate assertion claims locally before sending\nconst now = Math.floor(Date.now()/1000);\nif (assertion.payload.exp < now) throw new Error('client assertion expired');\nif (assertion.payload.iss !== clientId || assertion.payload.sub !== clientId) throw new Error('assertion iss/sub must equal client_id');","typeGuard":null,"tryCatchPattern":"try {\n  const token = await exchangeWithClientAssertion(assertion);\n} catch (e) {\n  if (e.message.includes('invalid_client')) regenerateAssertionAndRetry();\n  else throw e;\n}","preventionTips":["Regenerate assertions per request with short exp (e.g. 5 minutes) and correct aud (token endpoint URL).","Keep client JWKS in sync with signing keys; set kid in the JWT header.","Sync NTP on the client host to avoid clock-skew expiry issues."],"tags":["oidc","jwt","signature-verification","client-authentication"],"backgroundTag":"jwt-token-expired","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"}