Implement Multi-Factor Authentication in Spring Boot 3 with One‑Time Tokens
This guide explains how to add MFA to a Spring Boot 3 application using password plus one‑time token authentication, covering the theory of MFA factors, required dependencies, API definitions, security configuration, token‑generation handling, custom login pages, and the complete verification flow with code snippets and screenshots.
Introduction
Multi‑Factor Authentication (MFA) requires more than one credential type. The common factors in web applications are a password (something you know) and a one‑time token (something you have).
Demo Setup
Environment
Spring Boot 3.5.0 with spring-boot-starter-security dependency.
API Endpoints
@RestController
public class HomeController {
@GetMapping("/admin")
public String admin() { return "Admin Page"; }
@GetMapping("/ott/sent")
String ottSent() { return "OneTimeToken Sent"; }
}The /admin endpoint is protected; /ott/sent serves as a post‑token‑generation confirmation page.
Security Configuration (MFA)
@Configuration
@EnableWebSecurity
@EnableMultiFactorAuthentication(authorities = {
FactorGrantedAuthority.PASSWORD_AUTHORITY,
FactorGrantedAuthority.OTT_AUTHORITY })
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/ott/sent", "/login.html", "/ott.html",
"/css/**", "/error").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN"))
.formLogin(form -> form
.loginPage("/login.html")
.loginProcessingUrl("/login"))
.oneTimeTokenLogin(ott -> ott
.loginPage("/ott.html")
.loginProcessingUrl("/ott/generate"))
.build();
}
@Bean
UserDetailsService userDetailsService() {
var user = User.withUsername("user")
.password("{noop}666666")
.roles("USER")
.build();
var admin = User.withUsername("admin")
.password("{noop}888888")
.roles("ADMIN", "USER")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
}The @EnableMultiFactorAuthentication annotation adds both password and OTT authorities to every authorization rule.
One‑Time Token Generation Success Handler
@Component
public class OttSuccessHandler implements OneTimeTokenGenerationSuccessHandler {
private final OneTimeTokenGenerationSuccessHandler redirectHandler =
new RedirectOneTimeTokenGenerationSuccessHandler("/ott/sent");
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
OneTimeToken oneTimeToken) throws IOException, ServletException {
String link = ServletUriComponentsBuilder.fromCurrentContextPath()
.path("/login/ott")
.queryParam("token", oneTimeToken.getTokenValue())
.toUriString();
// In production send the link via email, SMS, etc.
System.out.println("Jump link: %s".formatted(link));
redirectHandler.handle(request, response, oneTimeToken);
}
}The handler builds a URL containing the generated token and prints it to the console.
Custom Authentication Pages
login.html – username/password login page:
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-body">
<h3 class="card-title text-center mb-4">Please Login</h3>
<form method="post" action="/login">
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input type="text" class="form-control" id="username" name="username" placeholder="Enter username">
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" placeholder="Enter password">
</div>
<button type="submit" class="btn btn-primary w-100">Login</button>
</form>
</div>
</div>
</div>
</div>
</div>ott.html – one‑time token request page:
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-body">
<h3 class="card-title text-center mb-4">Request One‑Time Token</h3>
<form method="post" action="/ott/generate">
<div class="mb-3">
<label for="otp-username" class="form-label">Username</label>
<input type="text" class="form-control" id="otp-username" name="username" placeholder="Enter username">
</div>
<button type="submit" class="btn btn-primary w-100">Send Token</button>
</form>
</div>
</div>
</div>
</div>
</div>Verification Flow
Client requests /admin → Spring Security redirects to login.html.
After successful username/password authentication, the user is redirected to ott.html to request a one‑time token.
Submitting the form generates a token; OttSuccessHandler prints a link such as http://localhost:8080/login/ott?token=….
Opening the link shows the /ott/sent confirmation page and then redirects back to the originally requested /admin endpoint, completing MFA.
Console output example:
Jump link: http://localhost:8080/login/ott?token=46c7ed8b-367c-4b63-a5a6-425f0f793f1bThis example demonstrates how to integrate password‑plus‑one‑time‑token MFA into a Spring Boot 3 application, covering dependencies, controller definition, security configuration, token handling, custom UI, and the end‑to‑end verification process.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
