apereo/cas · error · IllegalArgumentException

Redirect URI cannot contain a fragment

Error message

Redirect URI cannot contain a fragment

What it means

During OIDC dynamic client registration, the translator validates the registration request before creating an OidcRegisteredService. The OIDC spec forbids fragment components ('#') in redirect URIs, so any redirect URI containing '#' causes an IllegalArgumentException in translate().

Solutions

  1. Remove the fragment component from every redirect URI in the registration request
  2. Use path-based routing or a query parameter instead of a fragment for client-side routing state
  3. Validate redirect URIs client-side before submitting the registration request

Example fix

// before
"redirect_uris": ["https://app.example.com/callback#/home"]
// after
"redirect_uris": ["https://app.example.com/callback?route=home"]
Defensive patterns

Strategy: validation

Validate before calling

List<String> redirectUris = request.getRedirectUris();
if (redirectUris.stream().anyMatch(u -> u.contains("#"))) {
    throw new IllegalArgumentException("redirect_uris must not contain fragments");
}

Type guard

boolean hasNoFragment = redirectUris.stream().allMatch(u -> u.indexOf('#') < 0);

Prevention

When it happens

Trigger: Calling the dynamic client registration endpoint (translate in OidcDefaultClientRegistrationRequestTranslator) with a registration_request whose redirect_uris array contains at least one URI including a '#' character.

Common situations: Developers embedding a default fragment (e.g. 'https://app/callback#/home') copied from SPA frameworks, or building redirect URIs with hash-based routing; also test fixtures that accidentally include '#'.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at support/cas-server-support-oidc-core-api/src/main/java/org/apereo/cas/oidc/web/controllers/dynareg/OidcDefaultClientRegistrationRequestTranslator.java:65

    private static final ObjectMapper MAPPER = JacksonObjectMapperFactory.builder()
        .defaultTypingEnabled(false).build().toObjectMapper();

    private static final int GENERATED_CLIENT_NAME_LENGTH = 8;

    private final ObjectProvider<OidcConfigurationContext> configurationContext;

    @Override
    public OidcRegisteredService translate(
        final OidcClientRegistrationRequest registrationRequest,
        final Optional<OidcRegisteredService> givenService) throws Exception {

        val context = configurationContext.getObject();

        val containsFragment = registrationRequest.getRedirectUris()
            .stream()
            .anyMatch(uri -> uri.contains("#"));
        if (containsFragment) {
            throw new IllegalArgumentException("Redirect URI cannot contain a fragment");
        }

        val registeredService = givenService.orElseGet(OidcRegisteredService::new);
        if (StringUtils.isNotBlank(registrationRequest.getClientName())) {
            registeredService.setName(registrationRequest.getClientName());
        } else if (StringUtils.isBlank(registeredService.getName())) {
            registeredService.setName(RandomUtils.randomAlphabetic(GENERATED_CLIENT_NAME_LENGTH));
        }

        val serviceId = String.join("|", registrationRequest.getRedirectUris());
        registeredService.setServiceId(serviceId);

        registeredService.setSectorIdentifierUri(registrationRequest.getSectorIdentifierUri());
        registeredService.setSubjectType(registrationRequest.getSubjectType());
        if (Strings.CI.equals(OidcSubjectTypes.PAIRWISE.getType(), registeredService.getSubjectType())) {
            registeredService.setUsernameAttributeProvider(new PairwiseOidcRegisteredServiceUsernameAttributeProvider());
        }

View on GitHub (pinned to e7288fc434)