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
- Send subject_token_type=urn:ietf:params:oauth:token-type:access_token and supply an existing CAS OAuth access token as subject_token
- 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
- Check for typos/URI casing in subject_token_type — it must match the supported enum values exactly
- If a new token type is genuinely needed, extend the switch in extractSubjectTokenExchangeRequest in a custom extractor subclass
- 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
- Use the RFC 8693 token-type URN constants verbatim
- Pin client token-exchange payloads to tested subject_token_type values
- Add a preflight check against supported types before calling the endpoint
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
- Actor token type is not supported
- Requested grant type
- Subject token type is not supported
- Authentication request does contain a client id
- Cannot save a resource set with inconsistent scopes.
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)