{"record":{"id":"dc3fe829082e3bfe","repo":"spring-projects/spring-security","slug":"abstractuserdetailsauthenticationprovider-credenti","errorCode":"AbstractUserDetailsAuthenticationProvider.credentialsExpired","errorMessage":"User credentials have expired","messagePattern":"User credentials have expired","errorType":"exception","errorClass":"CredentialsExpiredException","httpStatus":null,"severity":"error","filePath":"core/src/main/java/org/springframework/security/authentication/AbstractUserDetailsReactiveAuthenticationManager.java","lineNumber":95,"sourceCode":"\t\t\tthrow new LockedException(this.messages.getMessage(\"AbstractUserDetailsAuthenticationProvider.locked\",\n\t\t\t\t\t\"User account is locked\"));\n\t\t}\n\t\tif (!user.isEnabled()) {\n\t\t\tthis.logger.debug(\"User account is disabled\");\n\t\t\tthrow new DisabledException(\n\t\t\t\t\tthis.messages.getMessage(\"AbstractUserDetailsAuthenticationProvider.disabled\", \"User is disabled\"));\n\t\t}\n\t\tif (!user.isAccountNonExpired()) {\n\t\t\tthis.logger.debug(\"User account is expired\");\n\t\t\tthrow new AccountExpiredException(this.messages\n\t\t\t\t.getMessage(\"AbstractUserDetailsAuthenticationProvider.expired\", \"User account has expired\"));\n\t\t}\n\t}\n\n\tprivate void defaultPostAuthenticationChecks(UserDetails user) {\n\t\tif (!user.isCredentialsNonExpired()) {\n\t\t\tthis.logger.debug(\"User account credentials have expired\");\n\t\t\tthrow new CredentialsExpiredException(this.messages.getMessage(\n\t\t\t\t\t\"AbstractUserDetailsAuthenticationProvider.credentialsExpired\", \"User credentials have expired\"));\n\t\t}\n\t}\n\n\t@Override\n\tpublic Mono<Authentication> authenticate(Authentication authentication) {\n\t\tString username = authentication.getName();\n\t\tString presentedPassword = (authentication.getCredentials() != null)\n\t\t\t\t? authentication.getCredentials().toString() : null;\n\t\t// @formatter:off\n\t\treturn retrieveUser(username)\n\t\t\t\t.doOnNext(this.preAuthenticationChecks::check)\n\t\t\t\t.publishOn(this.scheduler)\n\t\t\t\t.filter((userDetails) -> this.passwordEncoder.matches(presentedPassword, userDetails.getPassword()))\n\t\t\t\t.switchIfEmpty(Mono.defer(() -> Mono.error(new BadCredentialsException(\"Invalid Credentials\"))))\n\t\t\t\t.flatMap((userDetails) -> checkCompromisedPassword(presentedPassword).thenReturn(userDetails))\n\t\t\t\t.flatMap((userDetails) -> upgradeEncodingIfNecessary(userDetails, presentedPassword))\n\t\t\t\t.doOnNext(this.postAuthenticationChecks::check)","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/core/src/main/java/org/springframework/security/authentication/AbstractUserDetailsReactiveAuthenticationManager.java#L77-L113","documentation":"defaultPostAuthenticationChecks in AbstractUserDetailsReactiveAuthenticationManager runs after successful password verification and throws CredentialsExpiredException with 'User credentials have expired' (message code AbstractUserDetailsAuthenticationProvider.credentialsExpired) when isCredentialsNonExpired() returns false. The password was correct, but the credential's lifetime has ended, so authentication still fails.","triggerScenarios":"authenticate() where the UserDetails has correct credentials but isCredentialsNonExpired() returns false — e.g. password-rotation policies marking credentials stale, or custom UserDetails not overriding isCredentialsNonExpired() (default false).","commonSituations":"Corporate password-aging policies (90-day rotation); passwords flagged for mandatory reset; custom UserDetails implementations missing the override; CI/test credentials older than the rotation window.","solutions":["Return true from isCredentialsNonExpired() or reset the credential-expiry timestamp after a password change","Direct the user to a change-password flow when catching CredentialsExpiredException","Update passwordLastChanged on every password reset so expiry is computed from the right date","Verify your UserDetails implementation overrides isCredentialsNonExpired() — the interface default is false"],"exampleFix":"// before\n@Override public boolean isCredentialsNonExpired() { return false; }\n// after\n@Override public boolean isCredentialsNonExpired() {\n  return passwordChangedAt.plusDays(90).isAfter(Instant.now());\n}","handlingStrategy":"try-catch","validationCode":"if (!userDetails.isCredentialsNonExpired()) throw new CredentialsExpiredException(\"Password expired for \" + username);","typeGuard":"boolean passwordCurrent(UserDetails u) { return u.isCredentialsNonExpired(); }","tryCatchPattern":"authManager.authenticate(token)\n  .onErrorResume(CredentialsExpiredException.class, e -> redirectToChangePassword());","preventionTips":["Update passwordChangedAt on every password change","Return true from isCredentialsNonExpired() unless a rotation policy applies","Send proactive password-expiry notifications","Override isCredentialsNonExpired() in custom UserDetails (default false)"],"tags":["authentication","reactive","credentials-expired","password-policy"],"backgroundTag":"authentication-required","analyzedSha":"96852e8860138a482cb13d1479573f24ff6443c6","analyzedAt":"2026-09-10T23:25:23.477Z","contentChangedAt":"2026-09-10T23:25:23.477Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}