eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – Spring Sale 2026 – NPI EA (cat= Baeldung)
announcement - icon

Yes, we're now running our Spring Sale. All Courses are 30% off until 31st March, 2026

>> EXPLORE ACCESS NOW

Partner – Diagrid – NPI EA (cat= Testing)
announcement - icon

In distributed systems, managing multi-step processes (e.g., validating a driver, calculating fares, notifying users) can be difficult. We need to manage state, scattered retry logic, and maintain context when services fail.

Dapr Workflows solves this via Durable Execution which includes automatic state persistence, replaying workflows after failures and built-in resilience through retries, timeouts and error handling.

In this tutorial, we'll see how to orchestrate a multi-step flow for a ride-hailing application by integrating Dapr Workflows and Spring Boot:

>> Dapr Workflows With PubSub

Course – Spring Sale 2026 – NPI (cat=Baeldung)
announcement - icon

Yes, we're now running our Spring Sale. All Courses are 30% off until 31st March, 2026

>> EXPLORE ACCESS NOW

1. Overview

Providing a smooth log-in experience for a website requires a delicate balance. On one hand, we want users with various levels of computer savviness to be able to go through the log-in as quickly as possible. On the other hand, we need to ensure the identity of the person accessing our system – or risk a potentially disastrous security incident.

In this tutorial, we’ll show how to use one-time token logins in Spring Boot-based applications. This mechanism strikes a good balance between easy-of-use and security characteristics, and, as of Spring Boot version 3.4, is supported out-of-the-box when using Spring Security 6.4 or later.

2. What Is a One-Time Token Login?

The traditional way to identify a user in computer applications is to provide a form where he or she provides a username and password. Now, what if the user forgets his/her password? The common approach is to provide a “Forgot Password” button.

When the user clicks on this button, the backend sends a message to the user, which includes a time-limited token that allows the user to redefine his/her password.

However, for a range of applications, users are not expected to go to the site often and/or bother to save their passwords. In those cases, users tend to constantly use the reset password functionality, which generates frustration and, in some cases, angry customer support calls. Here are some applications that fall into this category:

  • Community sites (clubs, schools, churches, gaming)
  • Document distribution/signing services
  • Pop-up marketing sites

Instead, this is how the One-Time Token Login (or OTT, for short) mechanism works:

  1. The user informs his/her username, which usually corresponds to his/her email address
  2. The system generates a time-limited token and sends it using an out-of-band mechanism, which can be an email, SMS message, mobile notification, or similar
  3. The user opens the message in his email/messaging application and clicks on the link provided, which includes the one-time token
  4. The user’s device browser opens the link, which leads him back to the system’s OTT login location
  5. The system checks the token value embedded in the link. If it’s a valid one, access is granted, and the user can proceed. Alternatively, display a token submit form which, when submitted, completes the login process

3. When Should We Use OTT?

Before considering the One-Time Login mechanism for a given application, it’s a good idea to see its pros and cons:

Pros Cons
No need to manage user passwords, which also removes a security risk Single-factor-based authentication, at least from the application’s endpoint
Simple to use and understand even by non-tech-savvy users Vulnerable to man-in-the-middle attacks

We may now be thinking: why not use social logins? From a technical perspective, social logins, usually based on OAuth2/OIDC, are more secure than OTT.

However, enabling it requires more operational effort (e.g., requesting and maintaining client IDs for each provider) and may lead to decreased engagement, given the increased awareness regarding sharing personal data.

4. Implementing OTT With Spring Boot and Spring Security

Let’s create a simple Spring Boot application that uses the OTT support that has been available since version 3.4. As usual, we’ll start by adding the required Maven dependencies:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>3.4.1<version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>3.4.1<version>
</dependency>

The latest versions of these dependencies are available on Maven Central:

5. OTT Configuration

In the current version, enabling OTT for an application requires us to provide a SecurityFilterChain bean:

@Bean
SecurityFilterChain ottSecurityFilterChain(HttpSecurity http) throws Exception {
    return http
      .authorizeHttpRequests(ht -> ht.anyRequest().authenticated())
      .formLogin(withDefaults())
      .oneTimeTokenLogin(withDefaults())
      .build();
}

Here, the key point is the use of the new oneTimeTokenLogin() method that was introduced in version 6.4 as part of the DSL configuration. As usual, this method allows us to customize all aspects of the mechanism. In our case, however, we just use Customizer.withDefaults() to accept the default values.

Also, notice that we’ve added a formLogin() to the configuration. Without it, Spring Security will default to using Basic authentication, which does not play well with OTT.

Finally, in the authorizeHttpRequests() section, we’ve just added a configuration requiring authentication for all requests.

6. Sending the Token

The OTT mechanism has no built-in method that implements the actual token delivery to users. As explained in the documentation, this is a deliberate design decision since there are simply too many ways to implement this functionality.

Instead, OTT delegates this responsibility to the application code, which must expose a bean implementing the OneTimeTokenGenerationSuccessHandler interface. Alternatively, we can pass an implementation of this interface directly through the configuration DSL.

This interface has a single method, handle(), which takes the current servlet request, response, and, most importantly, a OneTimeToken object. The latter has the following attributes:

  • tokenValue: The generated token that we need to send to the user
  • username: The informed username
  • expiresAt: The Instant at which the generated token will expire

A typical implementation would go through the following steps:

  1. Use the supplied username as a key to look up the required delivery details. For instance, those details could include an email address or a phone number and the user’s locale settings
  2. Construct a URL that takes the user to the OTT login page
  3. Prepare and send a message with the OTT link to the user
  4. Send a redirect response to the client, which sends the browser to the OTT login page

In our implementation, we’ve opted to split the responsibilities related to steps 1 to 3 to a dedicated OttSenderService.

For step 4, we delegate the redirection details to Spring Security’s RedirectOneTimeTokenGenerationSuccessHandler. This is the resulting implementation:

public class OttLoginLinkSuccessHandler implements OneTimeTokenGenerationSuccessHandler {
    private final OttSenderService senderService;
    private final OneTimeTokenGenerationSuccessHandler redirectHandler = new RedirectOneTimeTokenGenerationSuccessHandler("/login/ott");

    // ... constructor omitted

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
      OneTimeToken oneTimeToken) throws IOException, ServletException {
        senderService.sendTokenToUser(oneTimeToken.getUsername(),
          oneTimeToken.getTokenValue(), oneTimeToken.getExpiresAt());
        redirectHandler.handle(request, response, oneTimeToken);
    }
}

Notice the “/login/ott” constructor argument passed to RedirectOneTimeTokenGenerationSuccessHandler. This corresponds to the default location for the token submission form and can be configured to a different location using the OTT DSL.

As for the OttSenderService, we’ll use a fake sender implementation that stores the token in a Map indexed by the username and logs its value:

public class FakeOttSenderService implements OttSenderService {
    private final Map<String,String> lastTokenByUser = new HashMap<>();

    @Override
    public void sendTokenToUser(String username, String token, Instant expiresAt) {
        lastTokenByUser.put(username, token);
        log.info("Sending token to username '{}'. token={}, expiresAt={}", username,token,expiresAt);
    }

    @Override
    public Optional<String> getLastTokenForUser(String username) {
        return Optional.ofNullable(lastTokenByUser.get(username));
    }
}

Notice that the OttSenderService has an optional method that allows us to recover the token for a username. The main purpose of this method is to simplify the implementation of unit tests, as we’ll see in the Automated Tests section.

7. Manual Testing

Let’s check the behavior of our application with the OTT mechanism with a simple navigation test. Once we’ve started it through the IDE or using mvn spring-boot:run, use your browser of choice and navigate to http://localhost:8080. The application will show in return a login page that contains both the standard form accepting username/password and the OTT form:

login form

Since we didn’t provide any UserDetailsService, Spring Boot’s auto-configuration creates a default one with a single user named “user”. Once we type it into the OTT’s form username field and click on the Send Token button, we should land on the token submission form:

token form

Now, if we take a look at the application logs, we’ll see a message like this:

c.b.s.ott.service.FakeOttSenderService   : Sending token to username 'user'. token=a0e3af73-0366-4e26-b68e-0fdeb23b9bb2, expiresAt=...

To complete the login process, just copy and paste the token value into the form and click on the Sign In button. As a result, we’ll get a welcome page which displays the current username:

home - welcome page

8. Automated Tests

Testing an OTT login flow requires navigating a sequence of pages, so we’ll use the Jsoup library to help us.

The full code goes through the same steps we’ve gone through in the manual test and adds checks along the way.

The only tricky part is getting access to the generated token. This is where the lookup method available on the OttSenderService interface comes in handy. Since we’re leveraging Spring Boot’s test infrastructure, we can simply inject the service into our test class and use it to query the token:

@Test
void whenLoginWithOtt_thenSuccess() throws Exception {
    // ... Jsoup setup and initial navigation omitted

    var optToken = this.ottSenderService.getLastTokenForUser("user");
    assertTrue(optToken.isPresent());

    var homePage = conn.newRequest(baseUrl + tokenSubmitAction)
      .data("token", optToken.get())
      .data("_csrf",csrfToken)
      .post();

    var username = requireNonNull(homePage.selectFirst("span#current-username")).text();
    assertEquals("user",username);
}

9. Conclusion

In this tutorial, we’ve described the One-Time Token Login mechanism and how to add it to a Spring Boot-based application.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

Course – Spring Sale 2026 – NPI EA (cat= Baeldung)
announcement - icon

Yes, we're now running our Spring Sale. All Courses are 30% off until 31st March, 2026

>> EXPLORE ACCESS NOW

Course – Spring Sale 2026 – NPI (All)
announcement - icon

Yes, we're now running our Spring Sale. All Courses are 30% off until 31st March, 2026

>> EXPLORE ACCESS NOW

eBook Jackson – NPI EA – 3 (cat = Jackson)
1 Comment
Oldest
Newest
Inline Feedbacks
View all comments