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
- Align cas.server.prefix with the externally reachable CAS base URL used when tokens are minted (including scheme, host, port, and context path, exactly).
- Ensure reverse proxies forward the original Host/X-Forwarded-* headers so the server prefix resolves identically at mint and validate time.
- Refresh the QR code after any server-prefix configuration change; old tokens keep the old issuer.
- 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
- Keep cas.server.prefix identical across all nodes and aligned with the external URL.
- Configure reverse proxies to forward original host headers.
- Re-mint QR tokens after any base-URL or context-path change.
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
- Token has expired
- Token does not belong to the assigned principal
- Request is assigned an invalid device identifier
- Private key JWT authentication method is not enabled for…
- Token encryption/signing is not enabled explicitly in the…
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)