{"record":{"id":"684f7d26059a979a","repo":"spring-projects/spring-security","slug":"ticket-validation-failure-message","errorCode":null,"errorMessage":"<ticket validation failure message>","messagePattern":"<ticket validation failure message>","errorType":"exception","errorClass":"BadCredentialsException","httpStatus":null,"severity":"error","filePath":"cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationProvider.java","lineNumber":156,"sourceCode":"\t\treturn result;\n\t}\n\n\tprivate CasAuthenticationToken authenticateNow(final Authentication authentication) throws AuthenticationException {\n\t\ttry {\n\t\t\tObject credentials = authentication.getCredentials();\n\t\t\tif (credentials == null) {\n\t\t\t\tthrow new BadCredentialsException(\"Authentication.getCredentials() cannot be null\");\n\t\t\t}\n\t\t\tAssertion assertion = this.ticketValidator.validate(credentials.toString(), getServiceUrl(authentication));\n\t\t\tUserDetails userDetails = loadUserByAssertion(assertion);\n\t\t\tthis.userDetailsChecker.check(userDetails);\n\t\t\tCollection<GrantedAuthority> authorities = new ArrayList<>(\n\t\t\t\t\tthis.authoritiesMapper.mapAuthorities(userDetails.getAuthorities()));\n\t\t\tauthorities.add(FactorGrantedAuthority.fromAuthority(AUTHORITY));\n\t\t\treturn new CasAuthenticationToken(this.key, userDetails, credentials, authorities, userDetails, assertion);\n\t\t}\n\t\tcatch (TicketValidationException ex) {\n\t\t\tthrow new BadCredentialsException(ex.getMessage(), ex);\n\t\t}\n\t}\n\n\t/**\n\t * Gets the serviceUrl. If the {@link Authentication#getDetails()} is an instance of\n\t * {@link ServiceAuthenticationDetails}, then\n\t * {@link ServiceAuthenticationDetails#getServiceUrl()} is used. Otherwise, the\n\t * {@link ServiceProperties#getService()} is used.\n\t * @param authentication\n\t * @return\n\t */\n\tprivate @Nullable String getServiceUrl(Authentication authentication) {\n\t\tString serviceUrl;\n\t\tif (authentication.getDetails() instanceof ServiceAuthenticationDetails) {\n\t\t\treturn ((ServiceAuthenticationDetails) authentication.getDetails()).getServiceUrl();\n\t\t}\n\t\tAssert.state(this.serviceProperties != null,\n\t\t\t\t\"serviceProperties cannot be null unless Authentication.getDetails() implements ServiceAuthenticationDetails.\");","sourceCodeStart":138,"sourceCodeEnd":174,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationProvider.java#L138-L174","documentation":"CasAuthenticationProvider.validateAuthentication (via authenticateNow) catches a TicketValidationException from the CAS service ticket validator and rethrows it as a BadCredentialsException whose message is the original exception's message. The thrown message is whatever the ticket validator reported, e.g. 'Ticket ST-... not recognized'. Spring Security does this so CAS validation failures map onto the standard bad-credentials authentication failure flow (including event publishing and failure handling).","triggerScenarios":"A user authenticates via CAS and CasAuthenticationProvider.authenticate -> authenticateNow calls the TicketValidator; the validator rejects the presented service ticket (expired, already used, service URL mismatch, or CAS server unreachable/rejecting).","commonSituations":"Ticket replay after browser refresh, clock skew between app server and CAS server, service/destination URL mismatch between the service ticket request and the validation request, misconfigured ticketValidator or serviceProperties, expired tickets, CAS server outage.","solutions":["Read the wrapped cause (ex.getCause(), the original TicketValidationException) to see the exact validation failure reported by the CAS server.","Verify the service URL used to request the ticket exactly matches the service parameter sent to /serviceValidate (use ServiceProperties and ServiceAuthenticationDetails consistently).","Check clock synchronization (NTP) between the application and the CAS server, since expired/not-yet-valid tickets commonly stem from skew.","Ensure the ticket is only validated once and not replayed on redirects or AJAX retries.","Confirm network connectivity and TLS trust between the app and the CAS server's validation endpoint."],"exampleFix":"// before: opaque message only\ntry { auth = provider.authenticate(token); } catch (BadCredentialsException e) { log.error(e.getMessage()); }\n// after: surface the CAS-side cause\ntry { auth = provider.authenticate(token); }\ncatch (BadCredentialsException e) {\n    Throwable cause = e.getCause(); // TicketValidationException with CAS server detail\n    log.error(\"CAS ticket validation failed: {}\", cause != null ? cause.getMessage() : e.getMessage());\n}","handlingStrategy":"try-catch","validationCode":"// verify service URL matches ServiceProperties before authenticating\nassert serviceProperties.getService().equals(request.getRequestURL().toString());","typeGuard":null,"tryCatchPattern":"try {\n    auth = provider.authenticate(token);\n} catch (BadCredentialsException e) {\n    Throwable cause = e.getCause();\n    log.warn(\"CAS ticket rejected: {}\", cause != null ? cause.getMessage() : e.getMessage());\n    // return 401 and redirect to CAS for a fresh ticket\n}","preventionTips":["Keep ServiceProperties and the CAS client service URL identical","Synchronize clocks via NTP with the CAS server","Never replay a ticket; redirect anonymous users to CAS instead","Monitor CAS server availability and TLS certificate trust"],"tags":["cas","authentication","sso","bad-credentials"],"backgroundTag":"authentication-required","analyzedSha":"96852e8860138a482cb13d1479573f24ff6443c6","analyzedAt":"2026-09-10T23:25:23.477Z","contentChangedAt":"2026-09-10T23:25:23.477Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}