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

  1. Omit actor_token entirely if delegation is not needed
  2. Use actor_token_type=urn:ietf:params:oauth:token-type:access_token with a valid CAS OAuth access token
  3. Use actor_token_type=urn:ietf:params:oauth:token-type:jwt with a JWT unpackable by the configured access-token JWT builder
  4. Confirm the actor token is still live in the ticket registry (expired tickets won't resolve for ACCESS_TOKEN either)
  5. 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

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


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)