apereo/cas · error · AuthenticationException

Token has an invalid issuer that does not match

Error message

Token %s has an invalid issuer %s that does not match %s

What it means

AuthenticationException thrown by DefaultQRAuthenticationTokenValidatorService.validate when the JWT issuer claim does not equal the configured CAS server prefix (cas.server.prefix). The issuer binds the token to the CAS deployment that created it.

Solutions

  1. Align cas.server.prefix with the externally reachable CAS base URL used when tokens are minted (including scheme, host, port, and context path, exactly).
  2. Ensure reverse proxies forward the original Host/X-Forwarded-* headers so the server prefix resolves identically at mint and validate time.
  3. Refresh the QR code after any server-prefix configuration change; old tokens keep the old issuer.
  4. Verify you are not mixing environments (staging QR with production CAS).

Example fix

// before
cas.server.prefix=https://old.example.org/cas
// after
cas.server.prefix=https://cas.example.org/cas
Defensive patterns

Strategy: validation

Validate before calling

// Compare the token issuer to the configured prefix before validating
if (!claims.getIssuer().equals(serverPrefix)) {
    throw new IllegalStateException("issuer mismatch: " + claims.getIssuer() + " vs " + serverPrefix);
}

Try / catch

try { validatorService.validate(request); } catch (AuthenticationException e) { if (e.getMessage().contains("invalid issuer")) { checkServerPrefixConfig(); } throw e; }

Prevention

When it happens

Trigger: claims.getIssuer() != casProperties.getServer().getPrefix() during QR token validation.

Common situations: cas.server.prefix changed (domain move, http->https, context path added) after tokens were minted; load balancer proxies rewriting the external URL so the issuer recorded at mint time differs; QR token minted by a staging environment and validated in production; trailing-slash mismatch in the configured prefix.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at support/cas-server-support-qr-authentication/src/main/java/org/apereo/cas/qr/validation/DefaultQRAuthenticationTokenValidatorService.java:60

        val now = LocalDateTime.now(Clock.systemUTC());
        if (now.isAfter(dt)) {
            LOGGER.trace("Comparing now at [{}] with token's expiration time [{}]", now, dt);
            throw new AuthenticationException(String.format("Token %s has expired", tgt.getId()));
        }

        val authentication = tgt.getAuthentication();
        LOGGER.trace("Authentication attempt linked to [{}] is [{}]", tgt.getId(), authentication);

        if (!authentication.getPrincipal().getId().equals(claims.getSubject())) {
            val message = String.format("Token %s does not belong to the assigned principal", claims.getSubject());
            throw new AuthenticationException(message);
        }

        if (!claims.getIssuer().equals(casProperties.getServer().getPrefix())) {
            val message = String.format("Token %s has an invalid issuer %s that does not match %s", tgt.getId(),
                claims.getIssuer(), casProperties.getServer().getPrefix());
            throw new AuthenticationException(message);
        }

        val tokenDeviceId = FunctionUtils.doUnchecked(() -> claims.getStringClaim(QRAuthenticationConstants.QR_AUTHENTICATION_DEVICE_ID));
        if (!Strings.CI.equals(tokenDeviceId, request.getDeviceId())) {
            LOGGER.warn("Request device identifier [{}] does not match the token's identifier: [{}]", request.getDeviceId(), tokenDeviceId);
            throw new AuthenticationException("Request is assigned an invalid device identifier");
        }

        if (!deviceRepository.isAuthorizedDeviceFor(request.getDeviceId(), claims.getSubject())) {
            val message = String.format("Token is not authorized for device identifier [%s]", request.getDeviceId());
            throw new AuthenticationException(message);
        }

        return QRAuthenticationTokenValidationResult.builder()
            .authentication(authentication)
            .build();
    }
}

View on GitHub (pinned to e7288fc434)