|  | @@ -65,7 +65,7 @@ Java::
 | 
	
		
			
				|  |  |  public class SecurityConfig {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      @Bean
 | 
	
		
			
				|  |  | -    public SecurityWebFilterChain filterChain(ServerHttpSecurity http, MagicLinkGeneratedOneTimeTokenHandler magicLinkSender) {
 | 
	
		
			
				|  |  | +    public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
 | 
	
		
			
				|  |  |          http
 | 
	
		
			
				|  |  |              // ...
 | 
	
		
			
				|  |  |              .formLogin(Customizer.withDefaults())
 | 
	
	
		
			
				|  | @@ -79,11 +79,11 @@ import org.springframework.mail.SimpleMailMessage;
 | 
	
		
			
				|  |  |  import org.springframework.mail.javamail.JavaMailSender;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  @Component <1>
 | 
	
		
			
				|  |  | -public class MagicLinkGeneratedOneTimeTokenHandler implements ServerGeneratedOneTimeTokenHandler {
 | 
	
		
			
				|  |  | +public class MagicLinkOneTimeTokenGenerationSuccessHandler implements ServerOneTimeTokenGenerationSuccessHandler {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      private final MailSender mailSender;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    private final ServerGeneratedOneTimeTokenHandler redirectHandler = new ServerRedirectGeneratedOneTimeTokenHandler("/ott/sent");
 | 
	
		
			
				|  |  | +    private final ServerOneTimeTokenGenerationSuccessHandler redirectHandler = new ServerRedirectOneTimeTokenGenerationSuccessHandler("/ott/sent");
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      // constructor omitted
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -119,14 +119,72 @@ class PageController {
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  | +----
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +Kotlin::
 | 
	
		
			
				|  |  | ++
 | 
	
		
			
				|  |  | +[source,kotlin,role="secondary"]
 | 
	
		
			
				|  |  | +----
 | 
	
		
			
				|  |  | +@Configuration
 | 
	
		
			
				|  |  | +@EnableWebFluxSecurity
 | 
	
		
			
				|  |  | +class SecurityConfig {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +         open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
 | 
	
		
			
				|  |  | +             return http {
 | 
	
		
			
				|  |  | +                authorizeExchange {
 | 
	
		
			
				|  |  | +                    authorize(anyExchange, authenticated)
 | 
	
		
			
				|  |  | +                 }
 | 
	
		
			
				|  |  | +                 oneTimeTokenLogin { }
 | 
	
		
			
				|  |  | +             }
 | 
	
		
			
				|  |  | +         }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +@Component (1)
 | 
	
		
			
				|  |  | +class MagicLinkOneTimeTokenGenerationSuccessHandler(val mailSender: MailSender): ServerOneTimeTokenGenerationSuccessHandler {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    private val redirectStrategy: ServerRedirectStrategy = DefaultServerRedirectStrategy()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    override fun handle(exchange: ServerWebExchange, oneTimeToken: OneTimeToken): Mono<Void> {
 | 
	
		
			
				|  |  | +        val builder = UriComponentsBuilder.fromUri(exchange.request.uri)
 | 
	
		
			
				|  |  | +            .replacePath(null)
 | 
	
		
			
				|  |  | +            .replaceQuery(null)
 | 
	
		
			
				|  |  | +            .fragment(null)
 | 
	
		
			
				|  |  | +            .path("/login/ott")
 | 
	
		
			
				|  |  | +            .queryParam("token", oneTimeToken.getTokenValue()) (2)
 | 
	
		
			
				|  |  | +        val magicLink = builder.toUriString()
 | 
	
		
			
				|  |  | +        builder.replacePath(null)
 | 
	
		
			
				|  |  | +            .replaceQuery(null)
 | 
	
		
			
				|  |  | +            .path("/ott/sent")
 | 
	
		
			
				|  |  | +        val redirectLink = builder.toUriString()
 | 
	
		
			
				|  |  | +        return this.mailSender.send(
 | 
	
		
			
				|  |  | +            getUserEmail(oneTimeToken.getUsername()), (3)
 | 
	
		
			
				|  |  | +            "Use the following link to sign in into the application: $magicLink") (4)
 | 
	
		
			
				|  |  | +        .then(this.redirectStrategy.sendRedirect(exchange, URI.create(redirectLink))) (5)
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        private String getUserEmail() {
 | 
	
		
			
				|  |  | +            // ...
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +@Controller
 | 
	
		
			
				|  |  | +class PageController {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    @GetMapping("/ott/sent")
 | 
	
		
			
				|  |  | +    fun ottSent(): String {
 | 
	
		
			
				|  |  | +        return "my-template"
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  ----
 | 
	
		
			
				|  |  |  ======
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -<1> Make the `MagicLinkGeneratedOneTimeTokenHandler` a Spring bean
 | 
	
		
			
				|  |  | +<1> Make the `MagicLinkOneTimeTokenGenerationSuccessHandler` a Spring bean
 | 
	
		
			
				|  |  |  <2> Create a login processing URL with the `token` as a query param
 | 
	
		
			
				|  |  |  <3> Retrieve the user's email based on the username
 | 
	
		
			
				|  |  | -<4> Use the `JavaMailSender` API to send the email to the user with the magic link
 | 
	
		
			
				|  |  | -<5> Use the `ServerRedirectOneTimeTokenGenerationSuccessHandler` to perform a redirect to your desired URL
 | 
	
		
			
				|  |  | +<4> Use the `MailSender` API to send the email to the user with the magic link
 | 
	
		
			
				|  |  | +<5> Use the `ServerRedirectStrategy` to perform a redirect to your desired URL
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  The email content will look similar to:
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -165,9 +223,36 @@ public class SecurityConfig {
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  @Component
 | 
	
		
			
				|  |  | -public class MagicLinkGeneratedOneTimeTokenHandler implements ServerGeneratedOneTimeTokenHandler {
 | 
	
		
			
				|  |  | +public class MagicLinkOneTimeTokenGenerationSuccessHandler implements ServerOneTimeTokenGenerationSuccessHandler {
 | 
	
		
			
				|  |  | +    // ...
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +----
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +Kotlin::
 | 
	
		
			
				|  |  | ++
 | 
	
		
			
				|  |  | +[source,kotlin,role="secondary"]
 | 
	
		
			
				|  |  | +----
 | 
	
		
			
				|  |  | +@Configuration
 | 
	
		
			
				|  |  | +@EnableWebFluxSecurity
 | 
	
		
			
				|  |  | +class SecurityConfig {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +         open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
 | 
	
		
			
				|  |  | +             return http {
 | 
	
		
			
				|  |  | +                 // ...
 | 
	
		
			
				|  |  | +                 formLogin { }
 | 
	
		
			
				|  |  | +                 oneTimeTokenLogin {
 | 
	
		
			
				|  |  | +                    generateTokenUrl = "/ott/my-generate-url"
 | 
	
		
			
				|  |  | +                 }
 | 
	
		
			
				|  |  | +             }
 | 
	
		
			
				|  |  | +         }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +@Component
 | 
	
		
			
				|  |  | +class MagicLinkOneTimeTokenGenerationSuccessHandler(val mailSender: MailSender): ServerOneTimeTokenGenerationSuccessHandler {
 | 
	
		
			
				|  |  |      // ...
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  ----
 | 
	
		
			
				|  |  |  ======
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -202,9 +287,36 @@ public class SecurityConfig {
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  @Component
 | 
	
		
			
				|  |  | -public class MagicLinkGeneratedOneTimeTokenHandler implements ServerGeneratedOneTimeTokenHandler {
 | 
	
		
			
				|  |  | +public class MagicLinkOneTimeTokenGenerationSuccessHandler implements ServerOneTimeTokenGenerationSuccessHandler {
 | 
	
		
			
				|  |  |      // ...
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  | +----
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +Kotlin::
 | 
	
		
			
				|  |  | ++
 | 
	
		
			
				|  |  | +[source,kotlin,role="secondary"]
 | 
	
		
			
				|  |  | +----
 | 
	
		
			
				|  |  | +@Configuration
 | 
	
		
			
				|  |  | +@EnableWebFluxSecurity
 | 
	
		
			
				|  |  | +class SecurityConfig {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +         open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
 | 
	
		
			
				|  |  | +             return http {
 | 
	
		
			
				|  |  | +                 // ...
 | 
	
		
			
				|  |  | +                 formLogin { }
 | 
	
		
			
				|  |  | +                 oneTimeTokenLogin {
 | 
	
		
			
				|  |  | +                    submitPageUrl = "/ott/submit"
 | 
	
		
			
				|  |  | +                 }
 | 
	
		
			
				|  |  | +             }
 | 
	
		
			
				|  |  | +         }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +@Component
 | 
	
		
			
				|  |  | +class MagicLinkOneTimeTokenGenerationSuccessHandler(val mailSender: MailSender): ServerOneTimeTokenGenerationSuccessHandler {
 | 
	
		
			
				|  |  | +    // ...
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  ----
 | 
	
		
			
				|  |  |  ======
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -251,9 +363,48 @@ public class MyController {
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  @Component
 | 
	
		
			
				|  |  | -public class MagicLinkGeneratedOneTimeTokenHandler implements ServerGeneratedOneTimeTokenHandler {
 | 
	
		
			
				|  |  | +public class MagicLinkOneTimeTokenGenerationSuccessHandler implements ServerOneTimeTokenGenerationSuccessHandler {
 | 
	
		
			
				|  |  | +    // ...
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +----
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +Kotlin::
 | 
	
		
			
				|  |  | ++
 | 
	
		
			
				|  |  | +[source,kotlin,role="secondary"]
 | 
	
		
			
				|  |  | +----
 | 
	
		
			
				|  |  | +@Configuration
 | 
	
		
			
				|  |  | +@EnableWebFluxSecurity
 | 
	
		
			
				|  |  | +class SecurityConfig {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +         open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
 | 
	
		
			
				|  |  | +             return http {
 | 
	
		
			
				|  |  | +                authorizeExchange {
 | 
	
		
			
				|  |  | +                    authorize(pathMatchers("/my-ott-submit"), permitAll)
 | 
	
		
			
				|  |  | +                    authorize(anyExchange, authenticated)
 | 
	
		
			
				|  |  | +                 }
 | 
	
		
			
				|  |  | +                 .formLogin { }
 | 
	
		
			
				|  |  | +                 oneTimeTokenLogin {
 | 
	
		
			
				|  |  | +                    showDefaultSubmitPage = false
 | 
	
		
			
				|  |  | +                 }
 | 
	
		
			
				|  |  | +             }
 | 
	
		
			
				|  |  | +         }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +@Controller
 | 
	
		
			
				|  |  | +class MyController {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    @GetMapping("/my-ott-submit")
 | 
	
		
			
				|  |  | +    fun ottSubmitPage(): String {
 | 
	
		
			
				|  |  | +        return "my-ott-submit"
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +@Component
 | 
	
		
			
				|  |  | +class MagicLinkOneTimeTokenGenerationSuccessHandler(val mailSender: MailSender): ServerOneTimeTokenGenerationSuccessHandler {
 | 
	
		
			
				|  |  |      // ...
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  ----
 | 
	
		
			
				|  |  |  ======
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -301,9 +452,39 @@ public class SecurityConfig {
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  @Component
 | 
	
		
			
				|  |  | -public class MagicLinkGeneratedOneTimeTokenHandler implements ServerGeneratedOneTimeTokenHandler {
 | 
	
		
			
				|  |  | +public class MagicLinkOneTimeTokenGenerationSuccessHandler implements ServerOneTimeTokenGenerationSuccessHandler {
 | 
	
		
			
				|  |  |      // ...
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  | +----
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +Kotlin::
 | 
	
		
			
				|  |  | ++
 | 
	
		
			
				|  |  | +[source,kotlin,role="secondary"]
 | 
	
		
			
				|  |  | +----
 | 
	
		
			
				|  |  | +@Configuration
 | 
	
		
			
				|  |  | +@EnableWebFluxSecurity
 | 
	
		
			
				|  |  | +class SecurityConfig {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +         open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
 | 
	
		
			
				|  |  | +             return http {
 | 
	
		
			
				|  |  | +                 //..
 | 
	
		
			
				|  |  | +                 .formLogin { }
 | 
	
		
			
				|  |  | +                 oneTimeTokenLogin { }
 | 
	
		
			
				|  |  | +             }
 | 
	
		
			
				|  |  | +         }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +         @Bean
 | 
	
		
			
				|  |  | +         open fun oneTimeTokenService():ReactiveOneTimeTokenService {
 | 
	
		
			
				|  |  | +             return MyCustomReactiveOneTimeTokenService();
 | 
	
		
			
				|  |  | +         }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +@Component
 | 
	
		
			
				|  |  | +class MagicLinkOneTimeTokenGenerationSuccessHandler(val mailSender: MailSender): ServerOneTimeTokenGenerationSuccessHandler {
 | 
	
		
			
				|  |  | +    // ...
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  ----
 | 
	
		
			
				|  |  |  ======
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -334,8 +515,34 @@ public class SecurityConfig {
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  @Component
 | 
	
		
			
				|  |  | -public class MagicLinkGeneratedOneTimeTokenHandler implements ServerGeneratedOneTimeTokenHandler {
 | 
	
		
			
				|  |  | +public class MagicLinkOneTimeTokenGenerationSuccessHandler implements ServerOneTimeTokenGenerationSuccessHandler {
 | 
	
		
			
				|  |  |      // ...
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  | +----
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +Kotlin::
 | 
	
		
			
				|  |  | ++
 | 
	
		
			
				|  |  | +[source,kotlin,role="secondary"]
 | 
	
		
			
				|  |  | +----
 | 
	
		
			
				|  |  | +@Configuration
 | 
	
		
			
				|  |  | +@EnableWebFluxSecurity
 | 
	
		
			
				|  |  | +class SecurityConfig {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +         open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
 | 
	
		
			
				|  |  | +             return http {
 | 
	
		
			
				|  |  | +                 //..
 | 
	
		
			
				|  |  | +                 .formLogin { }
 | 
	
		
			
				|  |  | +                 oneTimeTokenLogin {
 | 
	
		
			
				|  |  | +                    oneTimeTokenService = MyCustomReactiveOneTimeTokenService()
 | 
	
		
			
				|  |  | +                 }
 | 
	
		
			
				|  |  | +             }
 | 
	
		
			
				|  |  | +         }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +@Component
 | 
	
		
			
				|  |  | +class MagicLinkOneTimeTokenGenerationSuccessHandler(val mailSender: MailSender): ServerOneTimeTokenGenerationSuccessHandler {
 | 
	
		
			
				|  |  | +    // ...
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  ----
 | 
	
		
			
				|  |  |  ======
 |