apereo/cas · warning

Private key JWT authentication method is not enabled for…

Error message

Private key JWT authentication method is not enabled for CAS, or is not supported for service [{}]

What it means

CAS's OIDC JWT authenticator (used for client authentication at the token endpoint via client_secret_jwt / private_key_jwt) first checks that the server discovery document advertises these JWT auth methods and that the specific registered service allows one of them. If either fails, it refuses the credentials and returns empty, logging this warning.

Solutions

  1. Set the registered service's tokenEndpointAuthenticationMethod to client_secret_jwt or private_key_json (private_key_jwt) for that client.
  2. Ensure cas.authn.oidc.discovery.token-endpoint-auth-methods-supported includes client_secret_jwt and/or private_key_jwt.
  3. If JWT auth is intentionally disabled, switch the client SDK to client_secret_basic or client_secret_post instead.
  4. Reload/restart after changing service registration so ServicesManager picks up the new auth method.

Example fix

// before (service JSON)
"tokenEndpointAuthenticationMethod": "client_secret_basic"
// after
"tokenEndpointAuthenticationMethod": "private_key_json"
Defensive patterns

Strategy: validation

Validate before calling

// fetch discovery and check supported auth methods before using JWT auth
const disco = await fetch(`${issuer}/.well-known/openid-configuration`).then(r => r.json());
if (!disco.token_endpoint_auth_methods_supported?.some(m => m.endsWith('_jwt')))
  throw new Error('JWT client auth not supported by this CAS deployment');

Prevention

When it happens

Trigger: A client authenticates to the OIDC token endpoint using client_secret_jwt or private_key_jwt while either cas.authn.oidc.discovery.tokenEndpointAuthMethodsSupported excludes both JWT methods, or the registered service's tokenEndpointAuthenticationMethod is set to something else so OAuth20Utils.isTokenAuthenticationMethodSupportedFor fails.

Common situations: Client SDK defaults to private_key_jwt but the service registration says client_secret_basic; deployment overrode the discovery supported-auth-methods list; service JSON copied from another client with a different authentication method.

Understand the failure class

Related errors


AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08). Data as JSON: /api/errors/c1a331fe8ce47a7f. Report an issue: GitHub.

Appendix: source

Thrown at support/cas-server-support-oidc-core-api/src/main/java/org/apereo/cas/oidc/authn/OidcJwtAuthenticator.java:118

        return null;
    }


    @Override
    public Optional<Credentials> validate(final CallContext callContext, final Credentials creds) {
        return FunctionUtils.doAndHandle(() -> {
            val registeredService = getOidcRegisteredService(callContext);
            RegisteredServiceAccessStrategyUtils.ensureServiceAccessIsAllowed(registeredService);
            Objects.requireNonNull(registeredService, "regisetered service is null");

            if (OAuth20Utils.isAccessTokenRequest(callContext.webContext())) {
                val authMethodDisabled = oidcServerDiscoverySettings.getTokenEndpointAuthMethodsSupported()
                    .stream()
                    .map(OAuth20ClientAuthenticationMethods::parse)
                    .noneMatch(method -> method == OAuth20ClientAuthenticationMethods.CLIENT_SECRET_JWT || method == OAuth20ClientAuthenticationMethods.PRIVATE_KEY_JWT);
                if (authMethodDisabled || !OAuth20Utils.isTokenAuthenticationMethodSupportedFor(callContext, registeredService,
                    OAuth20ClientAuthenticationMethods.CLIENT_SECRET_JWT, OAuth20ClientAuthenticationMethods.PRIVATE_KEY_JWT)) {
                    LOGGER.warn("Private key JWT authentication method is not enabled for CAS, or is not supported for service [{}]", registeredService.getName());
                    return Optional.<Credentials>empty();
                }
            }

            val credentials = (UsernamePasswordCredentials) creds;
            val jwt = verifyCredentials(credentials, callContext.webContext());
            if (jwt == null) {
                LOGGER.warn("Unable to verify credentials");
                return Optional.<Credentials>empty();
            }

            val keys = new JsonWebKeySet();
            clientJwksRegistrationStore.ifAvailable(Unchecked.consumer(store -> {
                if (jwt instanceof final SignedJWT signedJWT) {
                    val jwk = signedJWT.getHeader().getJWK();
                    val kid = signedJWT.getHeader().getKeyID();
                    val jkt = jwk != null ? jwk.computeThumbprint().toString() : StringUtils.EMPTY;
                    store.findBy(registeredService.getClientId(), jkt)

View on GitHub (pinned to e7288fc434)