spring-projects/spring-security · error · AccessDeniedException

Can't change password as no Authentication object found in c

Error message

Can't change password as no Authentication object found in context for current user.

What it means

InMemoryUserDetailsManager.changePassword requires an authenticated user in the SecurityContextHolder: it reads the current Authentication to identify whose password to change. When the context holds no Authentication (null), it throws AccessDeniedException, since there is no user whose password could be changed. The manager treats the absence as a programming/configuration error, not a normal user error.

Source

Thrown at core/src/main/java/org/springframework/security/provisioning/InMemoryUserDetailsManager.java:138

		if (user instanceof MutableUserDetails mutable) {
			this.users.put(user.getUsername().toLowerCase(Locale.ROOT), mutable);
		}
		else {
			this.users.put(user.getUsername().toLowerCase(Locale.ROOT), new MutableUser(user));
		}
	}

	@Override
	public boolean userExists(String username) {
		return this.users.containsKey(username.toLowerCase(Locale.ROOT));
	}

	@Override
	public void changePassword(@Nullable String oldPassword, @Nullable String newPassword) {
		Authentication currentUser = this.securityContextHolderStrategy.getContext().getAuthentication();
		if (currentUser == null) {
			// This would indicate bad coding somewhere
			throw new AccessDeniedException(
					"Can't change password as no Authentication object found in context " + "for current user.");
		}
		String username = currentUser.getName();
		this.logger.debug(LogMessage.format("Changing password for user '%s'", username));
		// If an authentication manager has been set, re-authenticate the user with the
		// supplied password.
		if (this.authenticationManager != null) {
			this.logger.debug(LogMessage.format("Reauthenticating user '%s' for password change request.", username));
			this.authenticationManager
				.authenticate(UsernamePasswordAuthenticationToken.unauthenticated(username, oldPassword));
		}
		else {
			this.logger.debug("No authentication manager set. Password won't be re-checked.");
		}
		MutableUserDetails user = this.users.get(username.toLowerCase(Locale.ROOT));
		Assert.state(user != null, "Current user doesn't exist in database.");
		user.setPassword(newPassword);
	}

View on GitHub (pinned to 96852e8860)

Solutions

  1. Ensure the call runs inside an authenticated security context (e.g., within an authenticated request or with SecurityContextHolder.getContext().setAuthentication(...) set beforehand)
  2. In tests, authenticate first with SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(...)) or @WithMockUser
  3. If using a custom strategy, set it on the manager via setSecurityContextHolderStrategy and populate it consistently
  4. Propagate the SecurityContext to async threads (DelegatingSecurityContextExecutor) before calling changePassword

Example fix

// before
manager.changePassword("old", "new"); // no auth in context -> AccessDeniedException
// after
SecurityContextHolder.getContext().setAuthentication(
    new UsernamePasswordAuthenticationToken("user", "old",
        AuthorityUtils.createAuthorityList("ROLE_USER")));
manager.changePassword("old", "new");
Defensive patterns

Strategy: validation

Validate before calling

Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth == null || !auth.isAuthenticated()) {
    throw new IllegalStateException("changePassword requires an authenticated user");
}

Type guard

static boolean isAuthenticated() {
    Authentication a = SecurityContextHolder.getContext().getAuthentication();
    return a != null && a.isAuthenticated() && !(a instanceof AnonymousAuthenticationToken);
}

Try / catch

try {
    manager.changePassword(oldPassword, newPassword);
} catch (AccessDeniedException e) {
    if (e.getMessage().contains("no Authentication object")) {
        // redirect to login / abort password change
    } else { throw e; }
}

Prevention

When it happens

Trigger: Calling changePassword(oldPassword, newPassword) outside an authenticated request thread; clearing SecurityContextHolder programmatically before the call; using a custom SecurityContextHolderStrategy whose context was never populated; invoking it from a background/scheduled task or test without authenticating first.

Common situations: Password-change endpoints missing authentication configuration (anonymous access paths); tests calling changePassword directly without setUp authentication; async threads losing the SecurityContext because it is not propagated.

Understand the failure class

Related errors


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