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

  1. Scan a freshly generated QR code from the current logged-in session and retry.
  2. Verify principal id resolution is consistent between token generation and validation (same principal transformer/attribute as principal id settings).
  3. Check for impersonation/proxying configurations (impersonation, proxy authentication) that change the effective principal id of the TGT.
  4. 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

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


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)