apereo/cas · warning

Dynamic client registration mode is not configured as…

Error message

Dynamic client registration mode is not configured as protected.

What it means

CAS's dynamic client registration endpoint can be protected by an initial access token. This controller (initial-access-token mode) first checks that cas.authn.oidc.registration.dynamic-client-registration-mode is 'PROTECTED'; if it is ANY or omitted, it refuses the operation with HTTP 406 NOT_ACCEPTABLE because initial access tokens are meaningless in unprotected modes.

Solutions

  1. Set cas.authn.oidc.registration.dynamic-client-registration-mode=PROTECTED
  2. Obtain and supply a valid initial access token with the registration request
  3. If open registration is intended, use the endpoint/mode matching ANY and do not require an initial token
  4. Confirm the registered-mode flow via discovery metadata (registration_endpoint)

Example fix

// before
cas.authn.oidc.registration.dynamic-client-registration-mode=ANY
// after
cas.authn.oidc.registration.dynamic-client-registration-mode=PROTECTED
Defensive patterns

Strategy: validation

Validate before calling

if (registrationMode !== 'PROTECTED') {
  throw new Error('Initial access token registration requires dynamic-client-registration-mode=PROTECTED');
}
if (!initialAccessToken) throw new Error('Missing initial access token for protected registration');

Type guard

function supportsInitialAccessToken(cfg) {
  return cfg?.authn?.oidc?.registration?.dynamicClientRegistrationMode === 'PROTECTED';
}

Try / catch

try {
  return await registerClient(token);
} catch (e) {
  if (e.status === 406) {
    // set mode to PROTECTED or use the open registration flow
  }
  throw e;
}

Prevention

When it happens

Trigger: POST to the OIDC registration endpoint while the initial-access-token controller is active and dynamicClientRegistrationMode is not PROTECTED (e.g. mode left unset, set to ANY).

Common situations: Deployers forgetting to set dynamic-client-registration-mode=PROTECTED after enabling registration; copying example configs with mode=ANY; upgrading CAS where default mode changed; hitting the wrong registration controller variant for the configured mode.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at support/cas-server-support-oidc-core-api/src/main/java/org/apereo/cas/oidc/web/controllers/dynareg/OidcInitialAccessTokenController.java:101

    @GetMapping(value = {
        '/' + OidcConstants.BASE_OIDC_URL + '/' + OidcConstants.REGISTRATION_INITIAL_TOKEN_URL,
        "/**/" + OidcConstants.REGISTRATION_INITIAL_TOKEN_URL
    }, produces = MediaType.APPLICATION_JSON_VALUE)
    @Operation(summary = "Handle OIDC initial access token request")
    public ModelAndView handleRequestInternal(
        final HttpServletRequest request, final HttpServletResponse response) {   
        val webContext = new JEEContext(request, response);
        if (!getConfigurationContext().getIssuerService().validateIssuer(webContext, List.of(OidcConstants.REGISTRATION_INITIAL_TOKEN_URL))) {
            val body = OAuth20Utils.getErrorResponseBody(OAuth20Constants.INVALID_REQUEST, "Invalid issuer");
            val modelAndView = new ModelAndView(new JacksonJsonView(), body);
            modelAndView.setStatus(HttpStatus.BAD_REQUEST);
            return modelAndView;
        }
        val casProperties = getConfigurationContext().getCasProperties();
        val oidcProperties = casProperties.getAuthn().getOidc();

        if (!oidcProperties.getRegistration().getDynamicClientRegistrationMode().isProtected()) {
            LOGGER.warn("Dynamic client registration mode is not configured as protected.");
            return getBadRequestResponseEntity(HttpStatus.NOT_ACCEPTABLE);
        }
        val callContext = new CallContext(webContext, getConfigurationContext().getSessionStore(),
            getConfigurationContext().getOauthConfig().getProfileManagerFactory());
        return accessTokenClient.getCredentials(callContext)
            .map(credentials -> accessTokenClient.validateCredentials(callContext, credentials))
            .filter(Optional::isPresent)
            .map(Optional::get)
            .map(credentials -> {
                val principal = FunctionUtils.doUnchecked(() -> PrincipalFactoryUtils.newPrincipalFactory().createPrincipal(credentials.getUserProfile().getId()));
                val service = getConfigurationContext().getWebApplicationServiceServiceFactory()
                    .createService(casProperties.getServer().getPrefix());

                val tokenRequestContext = AccessTokenRequestContext
                    .builder()
                    .authentication(DefaultAuthenticationBuilder.newInstance().setPrincipal(principal).build())
                    .service(service)
                    .grantType(OAuth20GrantTypes.NONE)

View on GitHub (pinned to e7288fc434)