skip to content

What is ReactiveAuthenticationManager, how does it drive the authentication flow, and when would you implement a custom one?

level: seniorimportance: should knowfreq 45%

answer

  1. Mono<Authentication> authenticate(Authentication)
  2. AuthenticationWebFilter -> converter -> manager
  3. ReactiveSecurityContextHolder, not ThreadLocal
  4. DelegatingReactiveAuthenticationManager, first non-empty wins
  5. never block; Mono.error(BadCredentialsException)

basics

~10 s

ReactiveAuthenticationManager takes an unauthenticated Authentication and returns a Mono of a fully-authenticated one (or an error). It's the reactive strategy that actually verifies credentials, used by the authentication WebFilter.

solid answer

~40 s

ReactiveAuthenticationManager has one method: Mono<Authentication> authenticate(Authentication authentication). An authentication WebFilter (e.g. AuthenticationWebFilter) converts the incoming request into an unauthenticated Authentication token, hands it to the manager, and on a successful Mono result stores the principal in the ReactiveSecurityContextHolder; on error/empty it triggers the failure handler. The default for username/password is UserDetailsRepositoryReactiveAuthenticationManager, which uses a ReactiveUserDetailsService plus PasswordEncoder. You can compose several managers with DelegatingReactiveAuthenticationManager (first non-empty wins). You implement a custom one when you have non-standard credentials — API keys, opaque tokens verified by a remote call, custom JWT logic — returning a Mono so verification stays non-blocking. Return Mono.error(new BadCredentialsException(...)) on failure and never block; use reactive clients (WebClient) for remote checks.

code

java · 14 lines
java
@Bean
SecurityWebFilterChain chain(ServerHttpSecurity http,
                             ReactiveAuthenticationManager apiKeyManager) {
    AuthenticationWebFilter apiKeyFilter = new AuthenticationWebFilter(apiKeyManager);
    apiKeyFilter.setServerAuthenticationConverter(exchange ->
        Mono.justOrEmpty(exchange.getRequest().getHeaders().getFirst("X-API-Key"))
            .map(key -> new UsernamePasswordAuthenticationToken(key, key)));

    return http
        .authorizeExchange(e -> e.anyExchange().authenticated())
        .addFilterAt(apiKeyFilter, SecurityWebFiltersOrder.AUTHENTICATION)
        .csrf(ServerHttpSecurity.CsrfSpec::disable)
        .build();
}

go deeper

for a junior

Know it verifies credentials and returns Mono<Authentication>.

for a middle

Explain the default UserDetailsRepositoryReactiveAuthenticationManager and the filter->manager flow.

for a senior

Implement a custom manager with a WebFilter+converter, use DelegatingReactiveAuthenticationManager, and respect non-blocking rules.

for a principal

Design multi-mechanism auth (API keys + JWT + OAuth2), reason about context propagation, failure semantics, and event-loop safety.

## The contract ```java public interface ReactiveAuthenticationManager { Mono<Authentication> authenticate(Authentication authentication); } ``` Input: an **unauthenticated** `Authentication` (e.g. a `UsernamePasswordAuthenticationToken` holding username + raw password, or a `BearerTokenAuthenticationToken`). Output: - `Mono` emitting a **fully authenticated** `Authentication` (principal set, `isAuthenticated()==true`, authorities populated) on success; - `Mono.error(AuthenticationException)` (e.g. `BadCredentialsException`) on failure; - `Mono.empty()` to signal 'I can't handle this token' (useful when delegating). This is the reactive analogue of servlet `AuthenticationManager.authenticate(...)`. ## Where it sits in the flow 1. **`AuthenticationWebFilter`** intercepts the exchange. A `ServerAuthenticationConverter` turns the request (header, form body, cookie) into an unauthenticated `Authentication`, returning `Mono.empty()` when the request carries no credentials (so anonymous/other filters proceed). 2. The filter calls `authenticationManager.authenticate(token)`. 3. On success, a `ServerAuthenticationSuccessHandler` runs and the result is written into the **`ReactiveSecurityContextHolder`** (a `Context`-based, thread-independent holder — reactive code cannot use a `ThreadLocal`). 4. On error, a `ServerAuthenticationFailureHandler`/`ServerAuthenticationEntryPoint` runs (e.g. returns 401). 5. Downstream, authorization (`ReactiveAuthorizationManager`) reads the security context. ## Built-in implementations - **`UserDetailsRepositoryReactiveAuthenticationManager`** — username/password via `ReactiveUserDetailsService` + `PasswordEncoder` (the default for Basic/form login). - **`JwtReactiveAuthenticationManager`** — validates JWTs via a `ReactiveJwtDecoder` (resource-server). - **`OAuth2LoginReactiveAuthenticationManager`**, **`OidcAuthorizationCodeReactiveAuthenticationManager`** — OAuth2/OIDC login. - **`DelegatingReactiveAuthenticationManager`** — tries a list of managers in order, using the **first that emits a non-empty result**. ## Custom implementation Use a custom manager when credentials are non-standard: ```java public class ApiKeyReactiveAuthManager implements ReactiveAuthenticationManager { private final ApiKeyClient client; // uses WebClient, non-blocking @Override public Mono<Authentication> authenticate(Authentication auth) { String key = (String) auth.getCredentials(); return client.lookup(key) .map(account -> new UsernamePasswordAuthenticationToken( account.id(), null, List.of(new SimpleGrantedAuthority("ROLE_API")))) .switchIfEmpty(Mono.error(new BadCredentialsException("Invalid API key"))) .cast(Authentication.class); } } ``` Wire it via a custom `AuthenticationWebFilter` added at `SecurityWebFiltersOrder.AUTHENTICATION`: ```java AuthenticationWebFilter filter = new AuthenticationWebFilter(apiKeyManager); filter.setServerAuthenticationConverter(exchange -> { String key = exchange.getRequest().getHeaders().getFirst("X-API-Key"); return Mono.justOrEmpty(key) .map(k -> new UsernamePasswordAuthenticationToken(k, k)); }); http.addFilterAt(filter, SecurityWebFiltersOrder.AUTHENTICATION); ``` ## Rules and gotchas - **Never block.** No `.block()`, no blocking JDBC/`RestTemplate` — use `WebClient`/R2DBC. Blocking on the event loop can deadlock or starve the app. - Return **`Mono.error(BadCredentialsException)`** for genuine failures; return **`Mono.empty()`** only when you want another delegate to try. - The returned `Authentication` must be marked authenticated with authorities set; a half-built token can silently fail authorization. - **`ReactiveSecurityContextHolder`**, not `SecurityContextHolder` — the servlet `ThreadLocal` holder is meaningless in reactive code because work hops threads. - Don't confuse the manager (authenticates) with `ReactiveUserDetailsService` (loads users) or `ReactiveAuthorizationManager` (authorizes an already-authenticated principal). ## When to use / not Rely on the built-in managers for standard username/password, JWT, and OAuth2 flows. Implement a custom `ReactiveAuthenticationManager` only for bespoke credential schemes (API keys, HMAC signatures, custom token introspection).

  • Why must reactive security use ReactiveSecurityContextHolder instead of SecurityContextHolder?
    WebFlux processes a request across multiple threads on the event loop, so a ThreadLocal-based holder can't reliably carry the principal. ReactiveSecurityContextHolder stores it in the Reactor Context, which flows with the reactive chain.
  • How do you combine multiple authentication mechanisms?
    Wrap the managers in a DelegatingReactiveAuthenticationManager, which tries each in order and uses the first non-empty result; or add multiple AuthenticationWebFilters, each with its own converter and manager.

saying these in an interview costs you the question

  • Blocking (.block(), JDBC, RestTemplate) inside authenticate().
  • Using SecurityContextHolder (servlet ThreadLocal) in reactive code.
  • Returning a token without marking it authenticated / setting authorities.
  • Confusing ReactiveAuthenticationManager with ReactiveUserDetailsService or ReactiveAuthorizationManager.

context