How do you obtain the authenticated user inside a WebFlux controller or service?
answer
- @AuthenticationPrincipal in controllers
- Mono<Principal>/Authentication params work too
- getContext().map(getAuthentication)
- empty Mono == anonymous -> switchIfEmpty
- never .block() on the event loop
basics
~10 sEither inject the user as a method parameter (@AuthenticationPrincipal MyUser user, or a Mono<Authentication>/Principal argument), or in a service compose ReactiveSecurityContextHolder.getContext().map(SecurityContext::getAuthentication).
solid answer
~40 sThere are two idiomatic paths. In a controller, let Spring resolve it as an argument: `@AuthenticationPrincipal UserDetails user` (or your custom principal type), or accept a `Mono<Principal>` / `Authentication` parameter — Spring WebFlux's argument resolvers pull it from the Reactor Context for you. In a service or custom filter where you don't have argument resolution, use `ReactiveSecurityContextHolder.getContext()`, which returns `Mono<SecurityContext>`, then `.map(SecurityContext::getAuthentication)` to reach the `Authentication` (name, authorities, principal). Because it's a `Mono`, you compose it with `flatMap`/`map` into the rest of your pipeline rather than blocking. If the request is unauthenticated the Mono is empty, so use `switchIfEmpty`/`defaultIfEmpty` to handle the anonymous case. Never fall back to the ThreadLocal `SecurityContextHolder` in reactive code.
code
java · 17 lines@RestController
class ProfileController {
// Approach A: argument injection
@GetMapping("/me")
Mono<String> me(@AuthenticationPrincipal UserDetails user) {
return Mono.just(user.getUsername());
}
// Approach B: holder in a service
@GetMapping("/me2")
Mono<String> me2() {
return ReactiveSecurityContextHolder.getContext()
.map(ctx -> ctx.getAuthentication().getName())
.switchIfEmpty(Mono.error(new AccessDeniedException("anonymous")));
}
}go deeper
Know that you can inject @AuthenticationPrincipal or read getContext().
Should show composing the Mono and handling the empty/anonymous branch.
Should explain both come from the same Reactor Context and when to prefer each.
Should discuss testability, avoiding blocking, and consistent access patterns across layers.
## Two supported approaches ### 1. Argument injection in controllers (preferred) Spring WebFlux registers argument resolvers that read the reactive security context and hand you the user directly: - `@AuthenticationPrincipal` — injects the *principal* (often a `UserDetails` or your custom user object). You can even use a SpEL expression: `@AuthenticationPrincipal(expression = "claims['sub']")`. - A parameter of type `Authentication`, `Principal`, or their `Mono<...>` wrappers. ```java @GetMapping("/me") Mono<String> me(@AuthenticationPrincipal(expression = "username") String username) { return Mono.just(username); } ``` This keeps controllers clean and testable. ### 2. ReactiveSecurityContextHolder in services/filters Where no argument resolver runs (a `@Service`, a `WebFilter`, a repository), read it explicitly: ```java ReactiveSecurityContextHolder.getContext() .map(SecurityContext::getAuthentication) .flatMap(auth -> doWork(auth.getName())); ``` `getContext()` returns `Mono<SecurityContext>`. The `Authentication` gives: - `getName()` — username / subject - `getAuthorities()` — granted roles/scopes - `getPrincipal()` — the user object ## The empty case If nobody is authenticated, `getContext()` returns an **empty Mono** (completes with no value). If you `map` over it and the stream is empty, your downstream simply never runs. Handle it explicitly: ```java ReactiveSecurityContextHolder.getContext() .map(ctx -> ctx.getAuthentication().getName()) .switchIfEmpty(Mono.error(new AccessDeniedException("not authenticated"))); ``` ## Common mistakes - **Blocking:** calling `.block()` on the Mono inside an event-loop thread — throws `IllegalStateException` on non-blocking threads and stalls the loop otherwise. - **Wrong holder:** using `SecurityContextHolder.getContext()` (the ThreadLocal one) returns nothing useful in WebFlux. - **Assuming a value:** forgetting the empty/anonymous case and getting a silently short-circuited pipeline. ## When to use which Controllers → argument injection. Cross-cutting/service logic → `ReactiveSecurityContextHolder`. Both ultimately read the *same* Reactor Context that the security filter chain populated.
- What does getContext() emit when the request is not authenticated?An empty Mono — it completes without emitting a SecurityContext. Downstream map/flatMap operators are skipped, so you must use switchIfEmpty/defaultIfEmpty to handle the anonymous path explicitly.
- Why is @AuthenticationPrincipal usually preferred over calling the holder in a controller?It keeps the handler declarative and easy to unit-test, and Spring's argument resolver already handles reading and unwrapping the reactive context, avoiding manual Mono composition and the risk of blocking.
saying these in an interview costs you the question
- Assuming getContext() always emits, ignoring the empty/anonymous case
- Using the ThreadLocal SecurityContextHolder inside a reactive service
- Blocking on the Mono to 'get' the user synchronously