apereo/cas · error · AuthenticationException
Token is not authorized for device identifier
Error message
Token is not authorized for device identifier [%s]
What it means
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.
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
Example fix
// before: polling with an unregistered device id
val token = obtainToken();
validatorService.validate(QRAuthenticationTokenValidationRequest.builder().token(token).deviceId("unknown-device").build());
// after: use the deviceId returned at device registration
val token = obtainToken();
validatorService.validate(QRAuthenticationTokenValidationRequest.builder().token(token).deviceId(registeredDevice.getId()).build()); Defensive patterns
Strategy: validation
Validate before calling
if (!deviceRepository.isAuthorizedDeviceFor(request.getDeviceId(), claims.getSubject())) {
// skip token submission / force device re-registration first
return;
}
validatorService.validate(request); Type guard
boolean isDeviceAuthorized(QRAuthenticationTokenValidationRequest req, Authentication auth) {
return req.getDeviceId() != null
&& deviceRepository.isAuthorizedDeviceFor(req.getDeviceId(), auth.getPrincipal().getId());
} Try / catch
try {
validatorService.validate(request);
} catch (AuthenticationException e) {
// prompt device re-registration flow
logger.warn("Device not authorized for QR auth: {}", request.getDeviceId());
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "You do not have permission" / 403 Forbidden errors: authenticated but not allowed — causes and fixes across open-source libraries — this error's family across 31 libraries.
Related errors
- Principal attribute [
- Dn format cannot be empty/blank for authentication
- Unable to verify QR code
- Token has expired
- Token does not belong to the assigned principal
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/b5aa239d157e6b1d.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-qr-authentication/src/main/java/org/apereo/cas/qr/validation/DefaultQRAuthenticationTokenValidatorService.java:71
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)