skip to content

How do you obtain the authenticated user inside a WebFlux controller or service?

level: middleimportance: must knowfreq 60%

answer

  1. @AuthenticationPrincipal in controllers
  2. Mono<Principal>/Authentication params work too
  3. getContext().map(getAuthentication)
  4. empty Mono == anonymous -> switchIfEmpty
  5. never .block() on the event loop

basics

~10 s

Either 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 s

There 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
java
@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

for a junior

Know that you can inject @AuthenticationPrincipal or read getContext().

for a middle

Should show composing the Mono and handling the empty/anonymous branch.

for a senior

Should explain both come from the same Reactor Context and when to prefer each.

for a principal

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

context