spring-projects/spring-security · error · DisabledException

AbstractUserDetailsAuthenticationProvider.disabled

AbstractUserDetailsAuthenticationProvider.disabled

Error message

User is disabled

What it means

defaultPreAuthenticationChecks in AbstractUserDetailsReactiveAuthenticationManager throws DisabledException with 'User is disabled' (message code AbstractUserDetailsAuthenticationProvider.disabled) when UserDetails.isEnabled() returns false. The account exists but is flagged disabled, so authentication fails before credential checking.

Source

Thrown at core/src/main/java/org/springframework/security/authentication/AbstractUserDetailsReactiveAuthenticationManager.java:82

	private ReactiveUserDetailsPasswordService userDetailsPasswordService = ReactiveUserDetailsPasswordService.NOOP;

	private Scheduler scheduler = Schedulers.boundedElastic();

	private UserDetailsChecker preAuthenticationChecks = this::defaultPreAuthenticationChecks;

	private UserDetailsChecker postAuthenticationChecks = this::defaultPostAuthenticationChecks;

	private @Nullable ReactiveCompromisedPasswordChecker compromisedPasswordChecker;

	private void defaultPreAuthenticationChecks(UserDetails user) {
		if (!user.isAccountNonLocked()) {
			this.logger.debug("User account is locked");
			throw new LockedException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked",
					"User account is locked"));
		}
		if (!user.isEnabled()) {
			this.logger.debug("User account is disabled");
			throw new DisabledException(
					this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"));
		}
		if (!user.isAccountNonExpired()) {
			this.logger.debug("User account is expired");
			throw new AccountExpiredException(this.messages
				.getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired"));
		}
	}

	private void defaultPostAuthenticationChecks(UserDetails user) {
		if (!user.isCredentialsNonExpired()) {
			this.logger.debug("User account credentials have expired");
			throw new CredentialsExpiredException(this.messages.getMessage(
					"AbstractUserDetailsAuthenticationProvider.credentialsExpired", "User credentials have expired"));
		}
	}

	@Override

View on GitHub (pinned to 96852e8860)

Solutions

  1. Set the enabled flag true in your user store, or override isEnabled() to return true when appropriate
  2. Complete whatever activation flow (email verification) gates the enabled flag
  3. Catch DisabledException in the reactive chain and show an account-disabled message
  4. Verify your UserDetails implementation implements isEnabled() rather than relying on interface defaults

Example fix

// before
class AppUser implements UserDetails { /* isEnabled() not overridden -> false */ }
// after
class AppUser implements UserDetails {
  @Override public boolean isEnabled() { return this.active; }
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (!userDetails.isEnabled()) throw new DisabledException("Account disabled: " + username);

Type guard

boolean loginAllowed(UserDetails u) { return u.isEnabled(); }

Try / catch

authManager.authenticate(token)
  .onErrorResume(DisabledException.class, e -> Mono.error(new AuthFailure("ACCOUNT_DISABLED")));

Prevention

When it happens

Trigger: authenticate() receiving a UserDetails with isEnabled() == false — e.g. users.enabled=0 in the database, or a custom UserDetails class that does not override isEnabled() (interface default returns false).

Common situations: Newly registered users pending email verification; admin-deactivated accounts; custom UserDetails implementations forgetting to implement isEnabled(); schema migrations zeroing the enabled column.

Related errors


AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10). Data as JSON: /api/errors/385c28ceb4a1d986. Report an issue: GitHub.