apereo/cas · error

unauthorized_client

unauthorized_client

Error message

Client is not allowed to use the [%s] response type

What it means

Even when a response_type value is globally supported, the registered OAuth service must be permitted to use it. DefaultOAuth20RequestParameterResolver.isAuthorizedResponseTypeForService checks the service's supported grant types / response-type configuration; if the code flow's response_type is not authorized for this client, CAS fails the authorization request with error=unauthorized_client.

Solutions

  1. Set the service's supported grant types to include AUTHORIZATION_CODE (e.g. supportedGrantTypes in the registered service definition) via the service registry or management app.
  2. Confirm no custom OAuth20RequestParameterResolver bean overrides isAuthorizedResponseTypeForService in a way that rejects code for this service.
  3. Re-export/recreate the registered service JSON so grant-type fields survive upgrades; check cas.authn.oauth defaults if relying on global fallback.
  4. Use the response_type/grant type actually allowed for the client (e.g. token for implicit-only clients).

Example fix

// before (registered service JSON)
"supportedGrantTypes": ["CLIENT_CREDENTIALS"]
// after
"supportedGrantTypes": ["AUTHORIZATION_CODE", "REFRESH_TOKEN"]
Defensive patterns

Strategy: validation

Validate before calling

// before initiating code flow, confirm the client's registry entry permits it
if (!service.supportedGrantTypes?.includes('AUTHORIZATION_CODE')) {
  throw new Error('Client not authorized for authorization_code response type');
}

Prevention

When it happens

Trigger: GET /oauth2.0/authorize?response_type=code&client_id=X where registered service X does not list the authorization-code grant type (or its supportedGrantTypes/responseTypes config excludes it), or a custom OAuth20RequestParameterResolver bean denies it.

Common situations: Service registered for implicit/client-credentials only but client initiates code flow; service definition missing supportedGrantTypes after migration from an older CAS where defaults were broader; JSON service exported without the supportedGrantTypes/supportedResponseTypes fields.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


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

Appendix: source

Thrown at support/cas-server-support-oauth-core-api/src/main/java/org/apereo/cas/support/oauth/validator/authorization/OAuth20AuthorizationCodeResponseTypeAuthorizationRequestValidator.java:51

        final ServicesManager servicesManager,
        final ServiceFactory<WebApplicationService> webApplicationServiceServiceFactory,
        final AuditableExecution registeredServiceAccessStrategyEnforcer,
        final OAuth20RequestParameterResolver requestParameterResolver) {
        super(servicesManager, webApplicationServiceServiceFactory,
            registeredServiceAccessStrategyEnforcer, requestParameterResolver);
    }

    @Override
    public boolean validate(final WebContext context) {
        val clientIdResult = requestParameterResolver.resolveRequestParameter(context, OAuth20Constants.CLIENT_ID);
        return clientIdResult
            .map(this::getRegisteredServiceByClientId)
            .filter(Objects::nonNull)
            .map(registeredService -> {
                if (!requestParameterResolver.isAuthorizedResponseTypeForService(context, registeredService)) {
                    val responseTypeResult = requestParameterResolver.resolveRequestParameter(context, OAuth20Constants.RESPONSE_TYPE);
                    val msg = String.format("Client is not allowed to use the [%s] response type", responseTypeResult.orElse("unknown"));
                    LOGGER.warn(msg);
                    setErrorDetails(context, OAuth20Constants.UNAUTHORIZED_CLIENT, msg, true);
                    return false;
                }
                return true;
            })
            .orElse(false);
    }

    @Override
    public boolean supports(final WebContext context) throws Throwable {
        if (preValidate(context)) {
            val responseType = requestParameterResolver.resolveRequestParameter(context, OAuth20Constants.RESPONSE_TYPE)
                .map(String::valueOf)
                .orElse(StringUtils.EMPTY);
            LOGGER.debug("Requested response type is [{}]", responseType);
            return getSupportedResponseTypes()
                .stream()
                .anyMatch(allowedType -> OAuth20Utils.isResponseType(responseType, allowedType));

View on GitHub (pinned to e7288fc434)