{"record":{"id":"50b4dac37b9b7c3d","repo":"apereo/cas","slug":"token-s-does-not-belong-to-the-assigned-principal","errorCode":null,"errorMessage":"Token %s does not belong to the assigned principal","messagePattern":"Token (.+?) does not belong to the assigned principal","errorType":"exception","errorClass":"AuthenticationException","httpStatus":null,"severity":"error","filePath":"support/cas-server-support-qr-authentication/src/main/java/org/apereo/cas/qr/validation/DefaultQRAuthenticationTokenValidatorService.java","lineNumber":54,"sourceCode":"    public QRAuthenticationTokenValidationResult validate(final QRAuthenticationTokenValidationRequest request) {\n        val claims = jwtBuilder.unpack(request.getRegisteredService(), request.getToken());\n        LOGGER.trace(\"Unpacked QR token as [{}]\", claims);\n\n        val tgt = ticketRegistry.getTicket(claims.getJWTID(), TicketGrantingTicket.class);\n        val dt = DateTimeUtils.localDateTimeOf(claims.getExpirationTime());\n\n        val now = LocalDateTime.now(Clock.systemUTC());\n        if (now.isAfter(dt)) {\n            LOGGER.trace(\"Comparing now at [{}] with token's expiration time [{}]\", now, dt);\n            throw new AuthenticationException(String.format(\"Token %s has expired\", tgt.getId()));\n        }\n\n        val authentication = tgt.getAuthentication();\n        LOGGER.trace(\"Authentication attempt linked to [{}] is [{}]\", tgt.getId(), authentication);\n\n        if (!authentication.getPrincipal().getId().equals(claims.getSubject())) {\n            val message = String.format(\"Token %s does not belong to the assigned principal\", claims.getSubject());\n            throw new AuthenticationException(message);\n        }\n\n        if (!claims.getIssuer().equals(casProperties.getServer().getPrefix())) {\n            val message = String.format(\"Token %s has an invalid issuer %s that does not match %s\", tgt.getId(),\n                claims.getIssuer(), casProperties.getServer().getPrefix());\n            throw new AuthenticationException(message);\n        }\n\n        val tokenDeviceId = FunctionUtils.doUnchecked(() -> claims.getStringClaim(QRAuthenticationConstants.QR_AUTHENTICATION_DEVICE_ID));\n        if (!Strings.CI.equals(tokenDeviceId, request.getDeviceId())) {\n            LOGGER.warn(\"Request device identifier [{}] does not match the token's identifier: [{}]\", request.getDeviceId(), tokenDeviceId);\n            throw new AuthenticationException(\"Request is assigned an invalid device identifier\");\n        }\n\n        if (!deviceRepository.isAuthorizedDeviceFor(request.getDeviceId(), claims.getSubject())) {\n            val message = String.format(\"Token is not authorized for device identifier [%s]\", request.getDeviceId());\n            throw new AuthenticationException(message);\n        }","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-qr-authentication/src/main/java/org/apereo/cas/qr/validation/DefaultQRAuthenticationTokenValidatorService.java#L36-L72","documentation":"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.","triggerScenarios":"claims.getSubject() != tgt.getAuthentication().getPrincipal().getId() during QR token validation — the token's subject and the linked TGT's principal diverge.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Compare subject with the session's principal before submitting\nif (!claims.getSubject().equals(currentPrincipalId)) { regenerateToken(); }","typeGuard":null,"tryCatchPattern":"try { validatorService.validate(request); } catch (AuthenticationException e) { if (e.getMessage().contains(\"does not belong\")) { forceReauth(); } throw e; }","preventionTips":["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."],"tags":["qr-authentication","jwt","principal-mismatch"],"backgroundTag":"invalid-argument-value","analyzedSha":"e7288fc434b4f4505b8452e1a57e8fb3111bb863","analyzedAt":"2026-09-08T15:39:16.015Z","contentChangedAt":"2026-09-08T15:39:16.015Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}