apereo/cas · error · IllegalArgumentException

Subject token type is not supported

Error message

Subject token type %s is not supported

What it means

CAS's OAuth token-exchange grant extractor only supports a fixed set of subject_token_type values (access token and JWT). When the switch on subjectTokenType falls through to default, it throws this IllegalArgumentException, aborting the RFC 8693 token exchange before any token is issued.

Solutions

  1. Send subject_token_type=urn:ietf:params:oauth:token-type:access_token and supply an existing CAS OAuth access token as subject_token
  2. Or send subject_token_type=urn:ietf:params:oauth:token-type:jwt with a signed JWT the CAS access-token JWT builder can unpack and validate
  3. Check for typos/URI casing in subject_token_type — it must match the supported enum values exactly
  4. If a new token type is genuinely needed, extend the switch in extractSubjectTokenExchangeRequest in a custom extractor subclass
  5. Verify the token-exchange feature is configured on the registered service so the correct extractor handles the request

Example fix

// before
curl -d grant_type=urn:ietf:params:oauth:grant-type:token-exchange -d subject_token_type=urn:ietf:params:oauth:token-type:id_token ...
// after
curl -d grant_type=urn:ietf:params:oauth:grant-type:token-exchange -d subject_token_type=urn:ietf:params:oauth:token-type:jwt -d subject_token=<signed JWT> ...
Defensive patterns

Strategy: validation

Validate before calling

Set<String> supported = Set.of("urn:ietf:params:oauth:token-type:access_token","urn:ietf:params:oauth:token-type:jwt");
if (!supported.contains(subjectTokenType)) throw new IllegalArgumentException("Unsupported subject_token_type: " + subjectTokenType);

Type guard

boolean isSupportedSubjectTokenType(String t) { return t != null && Set.of("urn:ietf:params:oauth:token-type:access_token","urn:ietf:params:oauth:token-type:jwt").contains(t); }

Try / catch

try { exchange(...); } catch (IllegalArgumentException e) { return ResponseEntity.badRequest().body(Map.of("error","unsupported_token_type")); }

Prevention

When it happens

Trigger: POSTing a token-exchange grant (grant_type=urn:ietf:params:oauth:grant-type:token-exchange) with a subject_token_type value other than the supported ACCESS_TOKEN or JWT constants, e.g. 'id_token' or an arbitrary URI like urn:ietf:params:oauth:token-type:saml2.

Common situations: Clients implementing token exchange send a non-standard subject_token_type (SAML2, id_token, or a typo'd URN); service was configured expecting JWT but client sends opaque tokens; version drift where newer RFC token types are not yet supported by the CAS OAuth core module.

Related errors


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

Appendix: source

Thrown at support/cas-server-support-oauth-core-api/src/main/java/org/apereo/cas/support/oauth/web/response/accesstoken/ext/AccessTokenTokenExchangeGrantRequestExtractor.java:116

        val subjectToken = requestParameterResolver.resolveRequestParameter(webContext, OAuth20Constants.SUBJECT_TOKEN)
            .orElseThrow(() -> new IllegalArgumentException("Subject token cannot be undefined"));
        return switch (subjectTokenType) {
            case ACCESS_TOKEN -> {
                val token = configurationContext.getTicketRegistry().getTicket(subjectToken, OAuth20AccessToken.class);
                val registeredService = OAuth20Utils.getRegisteredOAuthServiceByClientId(configurationContext.getServicesManager(), token.getClientId());
                yield new TokenExchangeRequest(token, token.getService(), registeredService, token.getAuthentication());
            }
            case JWT -> {
                val claimSet = configurationContext.getAccessTokenJwtBuilder().unpack(Optional.empty(), subjectToken);
                val service = configurationContext.getWebApplicationServiceServiceFactory().createService(claimSet.getIssuer());
                service.getAttributes().put(OAuth20Constants.CLIENT_ID, List.of(claimSet.getIssuer()));
                val registeredService = OAuth20Utils.getRegisteredOAuthServiceByClientId(configurationContext.getServicesManager(), claimSet.getIssuer());
                val userProfile = extractUserProfile(webContext).orElseThrow();
                val authentication = configurationContext.getAuthenticationBuilder()
                    .build(userProfile, registeredService, webContext, service);
                yield new TokenExchangeRequest(claimSet, service, registeredService, authentication);
            }
            default -> throw new IllegalArgumentException("Subject token type %s is not supported".formatted(subjectTokenType));
        };
    }

    protected Authentication getActorTokenAuthentication(final WebContext webContext, final TokenExchangeRequest extractedRequest) throws Throwable {
        val configurationContext = getConfigurationContext().getObject();
        val actorToken = configurationContext.getRequestParameterResolver().resolveRequestParameter(webContext, OAuth20Constants.ACTOR_TOKEN);
        val actorTokenType = configurationContext.getRequestParameterResolver().resolveRequestParameter(webContext, OAuth20Constants.ACTOR_TOKEN_TYPE)
            .map(OAuth20TokenExchangeTypes::from);
        FunctionUtils.throwIf(actorToken.isPresent() && actorTokenType.isEmpty(),
            () -> new IllegalArgumentException("Actor token type cannot be undefined when actor token is provided"));
        if (actorToken.isPresent()) {
            val actorAuthentication = extractActorTokenAuthentication(webContext, extractedRequest);
            val tokenExchangePolicy = extractedRequest.registeredService() != null ? extractedRequest.registeredService().getTokenExchangePolicy() : null;
            if (tokenExchangePolicy == null || tokenExchangePolicy.canSubjectTokenActAs(extractedRequest.authentication(),
                actorAuthentication, actorTokenType.map(OAuth20TokenExchangeTypes::getType).orElse(null))) {
                return actorAuthentication;
            }
        }

View on GitHub (pinned to e7288fc434)