{"record":{"id":"b5aa239d157e6b1d","repo":"apereo/cas","slug":"token-is-not-authorized-for-device-identifier-s","errorCode":null,"errorMessage":"Token is not authorized for device identifier [%s]","messagePattern":"Token is not authorized for device identifier \\[(.+?)\\]","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":71,"sourceCode":"            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        }\n\n        return QRAuthenticationTokenValidationResult.builder()\n            .authentication(authentication)\n            .build();\n    }\n}\n","sourceCodeStart":53,"sourceCodeEnd":79,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-qr-authentication/src/main/java/org/apereo/cas/qr/validation/DefaultQRAuthenticationTokenValidatorService.java#L53-L79","documentation":"Thrown by DefaultQRAuthenticationTokenValidatorService.validate when the validated device repository says the device identifier carried in the QR token request is not an authorized device for the token's subject (claims.getSubject()). The token itself is valid, but the requesting device is not linked to that user, so CAS refuses the authentication. This is a device-trust/registration check, not a token signature check.","triggerScenarios":"A QR authentication token is presented with request.getDeviceId() that is valid (matches tokenDeviceId) but deviceRepository.isAuthorizedDeviceFor(deviceId, subject) returns false — i.e. the device was never registered/authorized for the authenticated user, or the authorization was revoked.","commonSituations":"User scans a QR code from a new/unregistered device; device registration data was wiped or changed in the backing store (Redis/JDBC/etc.); deviceId case/whitespace mismatch; revoking a device then continuing to poll with the old session.","solutions":["Register/authorize the device for the user via the device repository before submitting the QR token (re-run the device registration flow)","Verify the deviceId sent by the client exactly matches the one bound to the user at registration time (no case/whitespace drift)","Check the device repository backend (Redis/DB) has not been flushed or migrated and still holds the device-to-user authorization","Re-authenticate the QR session end-to-end if the device was intentionally revoked"],"exampleFix":"// before: polling with an unregistered device id\nval token = obtainToken();\nvalidatorService.validate(QRAuthenticationTokenValidationRequest.builder().token(token).deviceId(\"unknown-device\").build());\n// after: use the deviceId returned at device registration\nval token = obtainToken();\nvalidatorService.validate(QRAuthenticationTokenValidationRequest.builder().token(token).deviceId(registeredDevice.getId()).build());","handlingStrategy":"validation","validationCode":"if (!deviceRepository.isAuthorizedDeviceFor(request.getDeviceId(), claims.getSubject())) {\n    // skip token submission / force device re-registration first\n    return;\n}\nvalidatorService.validate(request);","typeGuard":"boolean isDeviceAuthorized(QRAuthenticationTokenValidationRequest req, Authentication auth) {\n    return req.getDeviceId() != null\n        && deviceRepository.isAuthorizedDeviceFor(req.getDeviceId(), auth.getPrincipal().getId());\n}","tryCatchPattern":"try {\n    validatorService.validate(request);\n} catch (AuthenticationException e) {\n    // prompt device re-registration flow\n    logger.warn(\"Device not authorized for QR auth: {}\", request.getDeviceId());\n}","preventionTips":["Always complete device registration before QR polling","Treat deviceId as opaque server-issued data, never user-typed","Audit device revocation so clients can detect and re-register"],"tags":["qr-authentication","device-authorization","cas"],"backgroundTag":"insufficient-permissions","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"}