Backend Development 9 min read

Master One-Time Token Login in Spring Boot 3: A Step‑by‑Step Guide

This article explains how to implement One-Time Token (OTT) authentication in Spring Boot 3.4 using Spring Security, covering environment setup, required dependencies, security configuration, token generation handlers, custom storage options, custom login pages, and testing procedures with complete code examples.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Master One-Time Token Login in Spring Boot 3: A Step‑by‑Step Guide

1. Introduction

One-Time Token (OTT) is a security mechanism that can be used only once, preventing replay attacks. Spring Security provides the oneTimeTokenLogin() DSL to support OTT authentication, allowing users to log in without additional account setup.

2. Practical Example

2.1 Environment Preparation

Spring Boot version: 3.4.0

Add the following dependencies:

<code>&lt;dependency&gt;
  &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
  &lt;artifactId&gt;spring-boot-starter-security&lt;/artifactId&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
  &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
  &lt;artifactId&gt;spring-boot-starter-mail&lt;/artifactId&gt;
&lt;/dependency&gt;</code>

2.2 Security Configuration with OTT

Create two simple controllers:

<code>@RestController
@RequestMapping("/api")
public class ApiController {
    @GetMapping("/query")
    public String query() { return "query"; }
}

@RestController
@RequestMapping("/sent")
public class SendController {
    @GetMapping("/ott")
    public String ott() {
        return "Authentication info has been sent to your email";
    }
}</code>

Configure the security filter chain to permit the /sent/** endpoint and enable OTT:

<code>@Configuration
public class SecurityConfig {
    @Bean
    SecurityFilterChain ottFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(registry -> {
                registry.requestMatchers("/sent/**").permitAll();
                registry.anyRequest().authenticated();
            })
            .formLogin(Customizer.withDefaults())
            .oneTimeTokenLogin(Customizer.withDefaults());
        return http.build();
    }
}</code>

2.3 OTT Success Handler

Implement a bean that builds a magic link and sends it by email:

<code>@Component
public class PackOneTimeTokenGenerationSuccessHandler implements OneTimeTokenGenerationSuccessHandler {
    private final OneTimeTokenGenerationSuccessHandler redirectHandler =
        new RedirectOneTimeTokenGenerationSuccessHandler("/sent/ott");

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, OneTimeToken ott)
            throws IOException, ServletException {
        UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(UrlUtils.buildFullRequestUrl(request))
            .replacePath(request.getContextPath())
            .replaceQuery(null)
            .fragment(null)
            .path("/login/ott")
            .queryParam("token", ott.getTokenValue());
        String magicLink = builder.toUriString();
        SimpleMailMessage mailMessage = new SimpleMailMessage();
        mailMessage.setSubject("Spring Boot OTT Login");
        mailMessage.setFrom("[email protected]");
        mailMessage.setTo("[email protected]");
        mailMessage.setText("OTT authentication, <a href=\"" + magicLink + "\">click to login</a>");
        try { this.mailSender.send(mailMessage); } catch (MailException ex) { System.err.println("Mail error: " + ex.getMessage()); }
        this.redirectHandler.handle(request, response, ott);
    }
}</code>

Note: The default token is stored in memory and expires after 5 minutes.

2.4 Custom Token Storage

Spring Security offers two implementations of OneTimeTokenService : InMemoryOneTimeTokenService (default) and JdbcOneTimeTokenService . To use a JDBC store, define a bean and create the table:

<code>@Bean
public JdbcOneTimeTokenService jdbcOneTimeTokenService(JdbcTemplate jdbcTemplate) {
    return new JdbcOneTimeTokenService(jdbcTemplate);
}

CREATE TABLE `one_time_tokens` (
  `token_value` varchar(36) NOT NULL,
  `username` varchar(50) NOT NULL,
  `expires_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`token_value`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
</code>

The service automatically schedules an hourly cleanup job.

2.5 Custom OTT Page

Create a custom HTML form that posts to /ott/generate and allow it through the security configuration:

<code>&lt;form id="ott-form" method="post" action="/ott/generate"&gt;
  &lt;h2&gt;OTT Login&lt;/h2&gt;
  &lt;p&gt;
    &lt;label for="ott-username"&gt;Username&lt;/label&gt;
    &lt;input type="text" id="ott-username" name="username" placeholder="Enter username" required&gt;
  &lt;/p&gt;
  &lt;button type="submit"&gt;Send Token&lt;/button&gt;
&lt;/form&gt;
</code>

Update the security matcher to permit *.html files:

<code>http.authorizeHttpRequests(registry -> {
    registry.requestMatchers("/sent/**", "*.html").permitAll();
});
</code>

2.6 Testing the OTT Flow

1. Access a protected endpoint such as /api/query . You will be redirected to the login page with two forms (username/password and OTT). 2. Enter the username and click “Send Token”. 3. Check the email, click the magic link, and then click “Sign in” on the token verification page. The whole process completes with minimal code.

All the above steps demonstrate how to integrate One-Time Token authentication into a Spring Boot 3 application.

JavaSpring BootSpring SecurityBackend AuthenticationOne-Time Token
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.