apereo/cas · error · AuthenticationException
Token does not belong to the assigned principal
Error message
Token %s does not belong to the assigned principal
What it means
AuthenticationException thrown by DefaultQRAuthenticationTokenValidatorService.validate when the JWT subject claim does not equal the principal id stored on the TicketGrantingTicket that the token's JWTID references. The QR token must belong to the same user whose TGT it points to.
Solutions
- Scan a freshly generated QR code from the current logged-in session and retry.
- Verify principal id resolution is consistent between token generation and validation (same principal transformer/attribute as principal id settings).
- Check for impersonation/proxying configurations (impersonation, proxy authentication) that change the effective principal id of the TGT.
- Ensure clients do not cache or replay old QR tokens across sessions.
Defensive patterns
Strategy: validation
Validate before calling
// Compare subject with the session's principal before submitting
if (!claims.getSubject().equals(currentPrincipalId)) { regenerateToken(); } Try / catch
try { validatorService.validate(request); } catch (AuthenticationException e) { if (e.getMessage().contains("does not belong")) { forceReauth(); } throw e; } Prevention
- Never reuse QR tokens across sessions or accounts.
- Keep principal-id resolution consistent between mint and validate.
- Audit impersonation/proxying configs that alter principal ids.
When it happens
Trigger: claims.getSubject() != tgt.getAuthentication().getPrincipal().getId() during QR token validation — the token's subject and the linked TGT's principal diverge.
Common situations: Token minted under one account but TGT reused/replaced after re-authentication or impersonation (proxy/impersonation flows changing principal id); tokens crafted or replayed from another session; principal id normalization differences (e.g. case or attribute-based id) between token creation and validation.
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
- Token has expired
- Token has an invalid issuer that does not match
- Request is assigned an invalid device identifier
- Unable to accept the ID token with an invalid [sub] claim
- Unknown authorization header type
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/50b4dac37b9b7c3d.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-qr-authentication/src/main/java/org/apereo/cas/qr/validation/DefaultQRAuthenticationTokenValidatorService.java:54
public QRAuthenticationTokenValidationResult validate(final QRAuthenticationTokenValidationRequest request) {
val claims = jwtBuilder.unpack(request.getRegisteredService(), request.getToken());
LOGGER.trace("Unpacked QR token as [{}]", claims);
val tgt = ticketRegistry.getTicket(claims.getJWTID(), TicketGrantingTicket.class);
val dt = DateTimeUtils.localDateTimeOf(claims.getExpirationTime());
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);
}View on GitHub (pinned to e7288fc434)