{"record":{"id":"b74618b46bf05838","repo":"apereo/cas","slug":"screen-service-error-message","errorCode":"screen.service.error.message","errorMessage":"Unauthorized: %s","messagePattern":"Unauthorized: (.+?)","errorType":"exception","errorClass":"UnauthorizedServiceException","httpStatus":403,"severity":"error","filePath":"core/cas-server-core-validation-api/src/main/java/org/apereo/cas/validation/AuthenticationPolicyAwareServiceTicketValidationAuthorizer.java","lineNumber":45,"sourceCode":"@RequiredArgsConstructor\npublic class AuthenticationPolicyAwareServiceTicketValidationAuthorizer implements ServiceTicketValidationAuthorizer {\n    private final ServicesManager servicesManager;\n\n    private final AuthenticationEventExecutionPlan authenticationEventExecutionPlan;\n\n    private final ConfigurableApplicationContext applicationContext;\n\n    @Override\n    public void authorize(final HttpServletRequest request, final Service service, final Assertion assertion) {\n        val registeredService = servicesManager.findServiceBy(service);\n        RegisteredServiceAccessStrategyUtils.ensureServiceAccessIsAllowed(service, registeredService);\n\n        LOGGER.debug(\"Evaluating service [{}] to ensure required authentication handlers can satisfy assertion\", service);\n        val primaryAuthentication = assertion.getPrimaryAuthentication();\n        val attributes = primaryAuthentication.getAttributes();\n        if (!attributes.containsKey(AuthenticationHandler.SUCCESSFUL_AUTHENTICATION_HANDLERS)) {\n            LOGGER.warn(\"No successful authentication handlers are recorded for the authentication attempt\");\n            throw UnauthorizedServiceException.denied(\"Unauthorized: %s\".formatted(service.getId()));\n        }\n        val successfulHandlerNames = CollectionUtils.toCollection(attributes.get(AuthenticationHandler.SUCCESSFUL_AUTHENTICATION_HANDLERS));\n        val assertedHandlers = authenticationEventExecutionPlan.resolveAuthenticationHandlers()\n            .stream()\n            .filter(BeanSupplier::isNotProxy)\n            .filter(handler -> successfulHandlerNames.contains(handler.getName()))\n            .collect(Collectors.toSet());\n\n        val policies = authenticationEventExecutionPlan.getAuthenticationPolicies(primaryAuthentication);\n        policies.forEach(policy -> {\n            try {\n                val simpleName = policy.getClass().getSimpleName();\n                LOGGER.trace(\"Executing authentication policy [{}]\", simpleName);\n                val result = policy.isSatisfiedBy(primaryAuthentication, assertedHandlers, applicationContext,\n                    Map.of(Assertion.class.getName(), assertion, RegisteredService.class.getName(), registeredService));\n                if (!result.isSuccess()) {\n                    throw UnauthorizedServiceException.denied(\"Unauthorized: %s\".formatted(service.getId()));\n                }","sourceCodeStart":27,"sourceCodeEnd":63,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/core/cas-server-core-validation-api/src/main/java/org/apereo/cas/validation/AuthenticationPolicyAwareServiceTicketValidationAuthorizer.java#L27-L63","documentation":"AuthenticationPolicyAwareServiceTicketValidationAuthorizer.authorize throws UnauthorizedServiceException (message 'Unauthorized: <serviceId>') during service ticket validation when the primary authentication record has no SUCCESSFUL_AUTHENTICATION_HANDLERS attribute, so the authorizer cannot verify that the required handlers satisfied the assertion.","triggerScenarios":"Validating an ST for a service whose registered service policy requires specific authentication handlers, when the assertion's primary authentication attributes lack the successful-handlers attribute — e.g. the ticket was issued by a flow/path that does not record handler names, or assertion data was reconstructed/serialized without it.","commonSituations":"Ticket issued by an older CAS node or a proxying chain that strips authentication attributes; custom authentication handlers not registering in the execution plan; policy on the service requiring handlers (requiredHandlers) while the SSO session predates that configuration.","solutions":["Ensure the authentication flow records AuthenticationHandler.SUCCESSFUL_AUTHENTICATION_HANDLERS in primary authentication attributes (default CAS behavior — check for customization).","Review the registered service's required-handler policy; relax requiredAuthenticationHandlers if handler tracking is not needed.","Force re-authentication (renew=true) so a fresh authentication record with handler attributes is created.","Verify all nodes in the cluster run a CAS version that propagates the handler attribute into assertions.","Check that resolved handlers' names actually match the recorded names (custom handler getName overrides)."],"exampleFix":"// before: service policy demands handlers but session lacks them\n// after: force fresh authentication at login\nhttps://cas.example.org/cas/login?service=...&renew=true\n// or in service registry JSON:\n\"requiredHandlers\": []","handlingStrategy":"try-catch","validationCode":"// Before relying on handler-based policy, confirm the attribute exists:\nvar handlers = assertion.getPrimaryAuthentication().getAttributes()\n    .get(AuthenticationHandler.SUCCESSFUL_AUTHENTICATION_HANDLERS);\nif (handlers == null || handlers.isEmpty()) {\n    LOGGER.warn(\"Assertion lacks successful-handler attributes; validation will be denied\");\n}","typeGuard":"boolean hasHandlerRecord(PrimaryAuthentication auth) {\n    return auth.getAttributes().containsKey(AuthenticationHandler.SUCCESSFUL_AUTHENTICATION_HANDLERS);\n}","tryCatchPattern":"try {\n    authorizer.authorize(request, response, assertion, registeredService);\n} catch (UnauthorizedServiceException e) {\n    LOGGER.error(\"Ticket validation denied for {}: {}\", service.getId(), e.getMessage());\n    // redirect user to re-authenticate with renew=true\n}","preventionTips":["Do not strip/rewrite primary authentication attributes in custom flows or ticket translators.","Keep handler names in the execution plan consistent with what services require.","Use renew=true when a stale SSO session may lack handler metadata.","Keep all cluster nodes on versions that record SUCCESSFUL_AUTHENTICATION_HANDLERS."],"tags":["authentication","service-ticket","authorization"],"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"}