apereo/cas · error · IllegalArgumentException
Actor token type is not supported
Error message
Actor token type %s is not supported
What it means
The actor_token in an RFC 8693 token-exchange request may only be an existing CAS OAuth access token or a JWT. Any other actor_token_type reaches the default branch of the switch and throws IllegalArgumentException, rejecting the delegation portion of the exchange.
Solutions
- Omit actor_token entirely if delegation is not needed
- Use actor_token_type=urn:ietf:params:oauth:token-type:access_token with a valid CAS OAuth access token
- Use actor_token_type=urn:ietf:params:oauth:token-type:jwt with a JWT unpackable by the configured access-token JWT builder
- Confirm the actor token is still live in the ticket registry (expired tickets won't resolve for ACCESS_TOKEN either)
- Subclass the extractor and override buildActorTokenAuthentication if a custom actor token type is required
Example fix
// before -d actor_token=RT-1234 -d actor_token_type=urn:ietf:params:oauth:token-type:refresh_token // after -d actor_token=AT-1234 -d actor_token_type=urn:ietf:params:oauth:token-type:access_token
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 (actorToken != null && !supported.contains(actorTokenType)) throw new IllegalArgumentException("Unsupported actor_token_type"); Type guard
boolean isSupportedActorTokenType(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","description","actor_token_type")); } Prevention
- Only attach actor_token when delegation is actually required
- Keep actor token in the same format as the subject token (access token or JWT)
- Verify actor tokens are live CAS tickets before sending
When it happens
Trigger: Including actor_token/actor_token_type in a token-exchange request with a type other than ACCESS_TOKEN or JWT (e.g. refresh token or SAML assertion) while calling extractActorTokenAuthentication via the extractor flow.
Common situations: Clients attempting actor impersonation/delegation with unsupported token formats; misconfigured client sends actor_token without the matching actor_token_type; older client libraries emitting deprecated actor token types.
Related errors
- Subject token type is not supported
- Requested grant type
- Subject token type is not supported
- Cannot save a resource set with inconsistent scopes.
- Cannot update a resource set without identifiers.
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/ea5be0113f628ef8.
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:159
.resolveRequestParameter(webContext, OAuth20Constants.ACTOR_TOKEN).orElseThrow();
val actorTokenType = configurationContext.getRequestParameterResolver()
.resolveRequestParameter(webContext, OAuth20Constants.ACTOR_TOKEN_TYPE)
.map(OAuth20TokenExchangeTypes::from)
.orElseThrow();
return buildActorTokenAuthentication(webContext, actorTokenType, actorToken);
}
protected Authentication buildActorTokenAuthentication(final WebContext webContext,
final OAuth20TokenExchangeTypes actorTokenType,
final String actorToken) throws Throwable {
val configurationContext = getConfigurationContext().getObject();
return switch (actorTokenType) {
case ACCESS_TOKEN -> {
val token = configurationContext.getTicketRegistry().getTicket(actorToken, OAuth20Token.class);
yield Objects.requireNonNull(token.getAuthentication());
}
case JWT -> buildActorTokenAuthenticationFromJwt(actorToken, webContext);
default -> throw new IllegalArgumentException("Actor token type %s is not supported".formatted(actorTokenType));
};
}
protected Authentication buildActorTokenAuthenticationFromJwt(final String actorToken, final WebContext webContext) throws Throwable {
val configurationContext = getConfigurationContext().getObject();
val claimSet = configurationContext.getAccessTokenJwtBuilder().unpack(Optional.empty(), actorToken);
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();
return configurationContext.getAuthenticationBuilder().build(userProfile, registeredService, webContext, service);
}
public record TokenExchangeRequest(Serializable token, Service service,
OAuthRegisteredService registeredService, Authentication authentication) {
}
}
View on GitHub (pinned to e7288fc434)