Search code examples
javaspringspring-bootkotlinwebflux

Webflux store result of Mono to session variable


My Spring Boot Webflux-application (Netty) outputs HTML using Thymeleaf-templates. In one handler-method I use WebClient to authenticate with an external id-provider. Now I'd like to store the resulting authentication token in a session-variable so I can reuse it with subsequent Controller/Handler methods. If I store the token in a model variable, Spring/Webflux resolves the Mono before rendering and provides the resulting token-String for use by the view (Thymeleaf template). But I can't find a way to store the token in a session variable. WebSession is enough for me as I won't run the application with multiple nodes.

Code I've tried (Kotlin):

@PostMapping("/login")
fun login(@Valid loginForm: LoginForm, model: Model, webSession: WebSession): String {
  val tokenMono: Mono<String> = loginService.login(loginForm.email, loginForm.password)
  model.addAttribute("token", tokenMono) // works and the token is available as a String in resulting view template
  webSession.addAttribute("token", tokenMono) // session-variable "token" is null in subsequent requests.
  return "login/success"
}

Solution

  • The following seems to work:

    @PostMapping("/login")
    fun login(@Valid loginForm: LoginForm, webSession: WebSession): Mono<String> {
       return loginService.login(loginForm.email, loginForm.password)
         .doOnNext { token ->
            webSession.attributes["token"] = token
         }
         .map { token -> "login/success" }
    }
    

    Key learnings:

    • The template view-name ("login/success" here) can be wrapped in a Mono.
    • Everything that needs to be done after the retrieval of a reactive type needs to be wrapped in one of the onXXX functions of Mono (maybe then works, too).
    • This also works for redirecting to a handler method that needs something from the reactive type's result. So say the original Mono is a future result of an R2DB insert. If I wanted to redirect to the resulting view, I could use .map { entity -> "redirect:/entity/${entity.id}" }.