A Complete Guide to MFA in Spring Security 7
Multi-Factor Authentication (MFA) is a security mechanism that requires users to verify their identity using multiple authentication methods before gaining access to an application. Traditionally, authentication relied only on username and password combinations. However, passwords alone are no longer sufficient because of phishing attacks, credential leaks, brute force attacks, and social engineering. MFA introduces additional layers of security such as passwords (something the user knows), OTP or TOTP codes (something the user has), and biometrics (something the user is). By combining multiple authentication factors, MFA significantly reduces the risk of unauthorized access even when passwords are compromised. Spring Security 7 introduces improved support and extensibility for MFA workflows, making it easier to implement enterprise-grade authentication systems. It provides flexible authentication pipelines that allow developers to apply MFA globally across applications, selectively on specific APIs or endpoints, or dynamically based on users, roles, devices, locations, risk scores, and custom business rules.
1. Understanding MFA in Spring Security
Multi-Factor Authentication (MFA) is a security mechanism that requires users to verify their identity using more than one authentication factor before gaining access to an application. Traditional authentication systems rely only on usernames and passwords, which can be compromised through phishing, credential stuffing, brute-force attacks, or data leaks. MFA significantly improves security by introducing an additional verification layer such as OTPs, authenticator apps, biometric verification, or hardware security keys. Spring Security 7 provides a highly flexible and extensible architecture for implementing MFA in enterprise-grade applications. Developers can combine authentication providers, security filters, authorization managers, session policies, and custom verification services to build adaptive authentication flows based on user roles, endpoints, device trust, login location, or business rules. Unlike older approaches where MFA logic was tightly coupled with login controllers, Spring Security 7 enables a modular implementation where the first-factor authentication and second-factor verification are handled independently within the security filter chain. This architecture makes MFA implementations cleaner, scalable, and easier to maintain.
In Spring Security 7, MFA can be implemented using:
- Custom Authentication Providers — Used to validate primary credentials and secondary authentication factors such as OTPs or TOTP codes.
- Authentication Filters — Intercept incoming authentication requests and trigger additional MFA validation steps before granting access.
- Authorization Managers — Enforce MFA requirements dynamically based on roles, URLs, risk conditions, or application modules.
- Session-based MFA Verification — Store MFA verification state in the authenticated session to avoid repeated OTP prompts during active sessions.
- OTP Verification Services — Integrate external or internal services for generating and validating OTPs using email, SMS, or authenticator applications.
- Conditional Security Rules — Apply MFA selectively based on time windows, user privileges, geographic location, suspicious activity, or sensitive APIs.
1.1 Why MFA is Important
Modern applications are exposed to increasing cybersecurity threats. Even strong passwords are no longer sufficient because attackers can obtain credentials through phishing attacks, malware, leaked databases, or social engineering. MFA adds an additional protection layer that drastically reduces the chances of unauthorized access. For example:
- A stolen password alone cannot grant access without the OTP.
- Authenticator applications generate short-lived rotating codes that are difficult to predict.
- Biometric authentication provides device-level identity verification.
- Adaptive MFA can enforce stricter authentication for high-risk logins.
Organizations commonly use MFA to secure:
- Banking and financial systems
- Healthcare applications
- Enterprise administration portals
- Cloud dashboards
- Government applications
- Internal HR and payroll systems
1.2 Authentication Factors
MFA works by combining multiple independent authentication factors. These factors generally belong to the following categories:
| Authentication Factor | Description | Examples |
|---|---|---|
| Something You Know | Information known only to the user | Password, PIN, Security Questions |
| Something You Have | A physical or digital device owned by the user | Mobile Device, Hardware Token, OTP App |
| Something You Are | Biometric identity verification | Fingerprint, Face Recognition, Retina Scan |
| Somewhere You Are | Location-based authentication | Office Network, Trusted Country |
| Something You Do | Behavioral verification patterns | Typing Pattern, Gesture Recognition |
1.3 Typical MFA Flow
A standard MFA authentication flow in Spring Security generally consists of two authentication stages. The first stage validates user credentials, while the second stage validates the additional authentication factor.
- User submits username and password
- Spring Security authenticates the credentials using AuthenticationManager
- Application marks the session as partially authenticated
- User is redirected to MFA verification page
- User enters OTP or TOTP code
- OTP verification service validates the second factor
- Spring Security updates the SecurityContext with full authentication
- User gains access to protected resources
This two-step authentication process ensures that even if credentials are compromised, unauthorized users still cannot access the system without the second verification factor.
1.4 MFA Architecture in Spring Security 7
Spring Security 7 allows developers to implement MFA using the standard Spring Security filter chain architecture. The framework enables developers to insert custom authentication filters and authorization checks at various stages of request processing. Spring Security 7 allows developers to implement MFA using the standard Spring Security filter chain architecture. The framework enables developers to insert custom authentication filters and authorization checks at various stages of request processing.
- UsernamePasswordAuthenticationFilter for primary login
- Custom OTPAuthenticationFilter for second-factor validation
- AuthenticationProvider implementations for OTP verification
- SecurityContext management for MFA session tracking
- Authorization rules that restrict access until MFA succeeds
- Custom success and failure handlers for MFA workflows
This modular architecture enables:
- Global MFA enforcement
- Endpoint-specific MFA rules
- Role-based MFA
- Time-based authentication policies
- Adaptive risk-based authentication
- Integration with external identity providers
1.5 Benefits of Using Spring Security for MFA
- Seamless integration with Spring Boot applications
- Flexible filter-chain based architecture
- Support for custom authentication mechanisms
- Easy integration with OAuth2 and OpenID Connect
- Built-in session and security context management
- Scalable for enterprise-grade systems
- Compatible with REST APIs and microservices
- Supports stateless JWT-based authentication flows
2. Complete Spring Boot MFA Example
2.1 Maven Dependencies
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.warrenstrange</groupId>
<artifactId>googleauth</artifactId>
<version>1.5.0</version>
</dependency>
</dependencies>
The spring-boot-starter-web dependency is used to build RESTful web applications and provides all required components for creating Spring MVC applications including embedded Tomcat server, REST controllers, request handling, JSON serialization, servlet APIs, and HTTP communication support. This dependency enables the application to expose APIs such as /login, /verify-otp, and protected endpoints used in the MFA workflow. The spring-boot-starter-security dependency adds Spring Security capabilities to the application and automatically configures authentication, authorization, password handling, session management, security filters, CSRF protection, and secure endpoint access. It forms the core security layer responsible for validating user credentials and integrating multi-factor authentication into the authentication pipeline. The googleauth dependency from the com.warrenstrange library provides support for Time-Based One-Time Password (TOTP) authentication compatible with authenticator applications such as Google Authenticator, Microsoft Authenticator, and Authy. This library internally implements the RFC 6238 TOTP algorithm and is responsible for generating and validating secure OTP codes using shared secret keys. Together, these dependencies provide the complete foundation required to develop a secure Spring Boot application with integrated multi-factor authentication support using Spring Security 7.
2.2 Security Configuration
package com.example.mfa.config;
import com.example.mfa.filter.MfaAuthenticationFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(
HttpSecurity http, MfaAuthenticationFilter mfaFilter) throws Exception {
http.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
// Public APIs
.requestMatchers("/login", "/verify-otp").permitAll()
// MFA required endpoint
.requestMatchers("/secure/**").authenticated()
// Admin endpoint
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated())
.formLogin(form -> form.defaultSuccessUrl("/mfa-required"));
http.addFilterAfter(
mfaFilter, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
The SecurityConfig class is the central Spring Security configuration class responsible for defining authentication, authorization, login handling, endpoint protection, and integrating the custom Multi-Factor Authentication filter into the Spring Security filter chain. The @Configuration annotation marks this class as a Spring configuration component so that Spring Boot automatically loads and processes the security configuration during application startup. Inside the class, the securityFilterChain() method is annotated with @Bean, which instructs Spring to register the returned SecurityFilterChain object as a managed bean responsible for configuring HTTP security behavior for the entire application. The method receives two parameters: HttpSecurity, which is the primary API used to configure web-based security in Spring Security 7, and MfaAuthenticationFilter, which is the custom filter responsible for validating MFA authentication rules after successful username-password login. The configuration begins with http.csrf(csrf -> csrf.disable()), which disables Cross-Site Request Forgery protection for simplicity in API-based authentication flows and testing environments, although CSRF should normally remain enabled in production web applications. The authorizeHttpRequests() section defines authorization rules for incoming HTTP requests. The statement .requestMatchers("/login", "/verify-otp").permitAll() allows unauthenticated users to access the login endpoint and OTP verification endpoint because these APIs must remain publicly accessible during the authentication process. The statement .requestMatchers("/secure/**").authenticated() ensures that all endpoints under the /secure path require successful user authentication before access is granted. The statement .requestMatchers("/admin/**").hasRole("ADMIN") applies role-based authorization by allowing access only to users having the ADMIN role, which demonstrates how Spring Security can combine role-based access control with MFA policies. The statement .anyRequest().authenticated() acts as a fallback security rule ensuring that every remaining endpoint in the application requires authentication unless explicitly permitted earlier. The .formLogin(form -> form.defaultSuccessUrl("/mfa-required")) configuration enables default Spring Security form-based authentication and redirects users to the /mfa-required endpoint after successful username-password authentication so that the MFA verification process can begin. Finally, the statement http.addFilterAfter() inserts the custom MfaAuthenticationFilter into the Spring Security filter chain immediately after the standard UsernamePasswordAuthenticationFilter. This ordering is important because the custom MFA filter should execute only after Spring Security has successfully authenticated the user’s username and password credentials. Once all security rules, filters, and authentication settings are configured, the method returns http.build(), which constructs the final immutable SecurityFilterChain instance used by Spring Security to process every incoming HTTP request in the application.
2.3 OTP Service
package com.example.mfa.service;
import com.warrenstrange.googleauth.GoogleAuthenticator;
import org.springframework.stereotype.Service;
@Service
public class OtpService {
private final GoogleAuthenticator googleAuthenticator = new GoogleAuthenticator();
public boolean verifyOtp(String secretKey, int otp) {
return googleAuthenticator.authorize(secretKey, otp);
}
}
The OtpService class is responsible for handling Time-Based One-Time Password (TOTP) verification logic used in the Multi-Factor Authentication workflow. The class is annotated with @Service, which marks it as a Spring service component and allows Spring Boot to automatically detect, instantiate, and manage it as a singleton bean inside the application context. This service acts as a dedicated business layer component for OTP validation, keeping MFA-related logic separate from controllers and security configuration classes to maintain clean architecture and better code organization. The class imports GoogleAuthenticator from the com.warrenstrange.googleauth library, which provides an implementation of the RFC 6238 Time-Based One-Time Password algorithm commonly used by authenticator applications such as Google Authenticator, Microsoft Authenticator, and Authy. The statement private final GoogleAuthenticator googleAuthenticator = new GoogleAuthenticator(); creates a reusable authenticator instance that internally handles OTP generation and verification using cryptographic time-based calculations. In TOTP authentication, both the server and the authenticator application share a common secret key, and OTP codes are generated dynamically based on the current timestamp and the shared secret. Since the generated OTP changes every 30 seconds, the codes remain valid only for a short duration, significantly improving security compared to static passwords. The method verifyOtp(String secretKey, int otp) accepts two parameters: the secretKey, which represents the shared secret associated with a specific user, and the otp, which is the one-time password entered by the user from their authenticator application. Inside the method, the statement googleAuthenticator.authorize(secretKey, otp) validates the provided OTP against the expected TOTP value generated using the shared secret key and the current system time. If the OTP is valid and falls within the acceptable time window, the method returns true; otherwise, it returns false. This validation mechanism ensures that only users possessing the registered authenticator device can successfully complete the second authentication factor. In enterprise applications, this service can be extended further to support features such as QR code generation, OTP expiration policies, backup recovery codes, account lockout protection, failed-attempt tracking, secret key encryption, device registration management, and adaptive MFA policies based on user risk assessment.
2.4 MFA Authentication Filter
package com.example.mfa.filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.time.LocalTime;
@Component
public class MfaAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String path = request.getRequestURI();
// Skip MFA verification APIs
if (path.equals("/verify-otp") || path.equals("/login")) {
filterChain.doFilter(request, response);
return;
}
Boolean verified = (Boolean) request.getSession().getAttribute("MFA_VERIFIED");
// Time-based MFA rule
int hour = LocalTime.now().getHour();
boolean officeHours = hour >= 9 && hour <= 18;
// Apply MFA after office hours
if (!officeHours) {
if (verified == null || !verified) {
response.sendError(
HttpServletResponse.SC_FORBIDDEN, "MFA verification required");
return;
}
}
filterChain.doFilter(request, response);
}
}
The MfaAuthenticationFilter class is a custom Spring Security filter responsible for enforcing Multi-Factor Authentication validation rules before allowing users to access protected resources in the application. The class is annotated with @Component, which allows Spring Boot to automatically detect and register it as a managed bean within the application context so it can later be injected into the Spring Security filter chain configuration. The class extends OncePerRequestFilter, a Spring-provided base filter class that guarantees the filter executes only once for every incoming HTTP request, preventing duplicate execution during internal request forwarding or asynchronous processing. The core logic is implemented inside the overridden doFilterInternal() method, which receives the current HttpServletRequest, HttpServletResponse, and FilterChain objects used to inspect, validate, and forward HTTP requests through the Spring Security processing pipeline. The statement String path = request.getRequestURI(); extracts the current request URI so the filter can determine which endpoint the user is trying to access. The first conditional block checks whether the incoming request is targeting /verify-otp or /login. These endpoints are intentionally excluded from MFA validation because users must first log in and verify their OTP before MFA status can be established. If the request matches either endpoint, the statement filterChain.doFilter(request, response) forwards the request to the next filter in the chain and immediately exits the method using return. The next statement retrieves the current MFA verification status from the HTTP session using request.getSession().getAttribute("MFA_VERIFIED"). This session attribute acts as a temporary flag indicating whether the user has already completed successful OTP verification during the current authenticated session. The returned value is typecast to a Boolean object and stored in the variable verified. The filter then implements a time-based MFA rule using LocalTime.now().getHour(), which retrieves the current server hour in 24-hour format. The statement boolean officeHours = hour >= 9 && hour <= 18; determines whether the current request occurs during standard office hours between 9 AM and 6 PM. This demonstrates how Spring Security filters can dynamically apply security rules based on time-sensitive business conditions. The next conditional block if (!officeHours) activates MFA enforcement only outside normal office hours, which is a common enterprise security requirement because after-hours access is often considered higher risk. Inside this block, the filter checks whether the user has completed MFA verification by evaluating verified == null || !verified. If the session does not contain the MFA flag or if the flag value is false, the request is considered unverified and access is denied. The statement response.sendError(HttpServletResponse.SC_FORBIDDEN, "MFA verification required") immediately returns an HTTP 403 Forbidden response to the client along with a descriptive error message indicating that MFA validation is mandatory. The method then exits using return so the request cannot continue further into the application. If the user has successfully completed MFA verification or if the request occurs during office hours, the final statement filterChain.doFilter(request, response) forwards the request to the next filter or controller in the processing pipeline, allowing normal application execution. This custom filter demonstrates how Spring Security 7 enables developers to implement advanced adaptive MFA strategies such as time-based authentication rules, session-based MFA validation, conditional access control, and dynamic security enforcement directly within the HTTP request lifecycle.
2.5 User-Based MFA Rules
package com.example.mfa.service;
import org.springframework.security.core.userdetails. * ;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class CustomUserService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// Admin requires MFA always
if ("admin".equals(username)) {
return User.withUsername("admin").password("{noop}password").roles("ADMIN").build();
}
// Normal user
return User.withUsername("user").password("{noop}password").roles("USER").build();
}
}
The CustomUserService class is responsible for loading and managing user authentication details within the Spring Security authentication process. The class is annotated with @Service, which marks it as a Spring-managed service component so that Spring Boot can automatically detect and register it in the application context. This class implements the UserDetailsService interface provided by Spring Security, which is one of the core extension points used for retrieving user information during authentication. Whenever a user attempts to log in, Spring Security internally invokes the loadUserByUsername() method to fetch user credentials, roles, and authorities associated with the provided username. The method signature loadUserByUsername(String username) accepts the username entered by the client during authentication and returns a UserDetails object that contains the authenticated user’s security metadata. If a user cannot be found, the method is expected to throw a UsernameNotFoundException, which signals authentication failure to Spring Security. Inside the method, a conditional rule is implemented to demonstrate user-based MFA enforcement logic. The statement if ("admin".equals(username)) checks whether the current user attempting to authenticate is an administrator. If the username matches admin, the method returns a Spring Security UserDetails object using the builder pattern provided by User.withUsername(). The returned user is configured with the username admin, password password, and the role ADMIN. The prefix {noop} in the password indicates that no password encoding is applied, meaning the password is stored in plain text for demonstration purposes only. In production applications, passwords should always be securely hashed using algorithms such as BCrypt or Argon2. The role assignment .roles("ADMIN") automatically grants the authority ROLE_ADMIN to the authenticated user, which can later be used by Spring Security authorization rules to restrict access to administrative endpoints. This section demonstrates a user-based MFA policy where administrator accounts are considered high-risk users and therefore always require Multi-Factor Authentication regardless of time-based or endpoint-based rules. If the username does not match admin, the method proceeds to create a standard user account using the username user, password password, and role USER. This demonstrates how different authentication and MFA policies can be dynamically applied based on user identity, privilege level, or business requirements. In real enterprise systems, user details are typically loaded from databases, LDAP servers, identity providers, or external authentication services rather than being hardcoded directly into the service class. Additionally, user-based MFA logic can be expanded further to support adaptive authentication policies such as mandatory MFA for finance users, conditional MFA for remote access users, device-based trust validation, geographic access restrictions, suspicious login detection, department-specific security controls, and risk-scoring mechanisms. This class therefore acts as the foundation for implementing identity-aware and role-aware authentication policies in Spring Security 7 applications.
2.6 OTP Verification Controller
package com.example.mfa.controller;
import com.example.mfa.service.OtpService;
import jakarta.servlet.http.HttpSession;
import org.springframework.web.bind.annotation. * ;
@RestController
public class AuthController {
private final OtpService otpService;
public AuthController(OtpService otpService) {
this.otpService = otpService;
}
@PostMapping("/verify-otp")
public String verifyOtp(@RequestParam String secret, @RequestParam int otp, HttpSession session) {
boolean valid = otpService.verifyOtp(secret, otp);
if (valid) {
session.setAttribute("MFA_VERIFIED", true);
return "MFA verification successful";
}
return "Invalid OTP";
}
@GetMapping("/secure/dashboard")
public String dashboard() {
return "Welcome to secure dashboard";
}
}
The AuthController class acts as the REST controller responsible for handling Multi-Factor Authentication verification requests and serving protected application endpoints in the Spring Boot application. The class is annotated with @RestController, which is a specialized Spring annotation combining @Controller and @ResponseBody, allowing all methods inside the class to automatically return HTTP response data directly in JSON or plain text format instead of rendering server-side views. This controller serves as the communication layer between client requests and the underlying MFA business logic implemented in the service layer. The class declares a private final field OtpService otpService, which represents the service responsible for validating Time-Based One-Time Passwords entered by users during MFA authentication. The constructor public AuthController(OtpService otpService) uses constructor-based dependency injection, allowing Spring Boot to automatically inject the OtpService bean at runtime. Constructor injection is considered a best practice in Spring applications because it promotes immutability, improves testability, and ensures required dependencies are always initialized during object creation. The method annotated with @PostMapping("/verify-otp") defines an HTTP POST endpoint exposed at /verify-otp that handles OTP verification requests submitted by authenticated users. The method accepts three parameters: @RequestParam String secret, which represents the shared secret key associated with the user’s authenticator application, @RequestParam int otp, which contains the one-time password entered by the user, and HttpSession session, which represents the current user session maintained by the server. The @RequestParam annotation instructs Spring to automatically extract request parameters from the incoming HTTP request and bind them to the corresponding method arguments. Inside the method, the statement boolean valid = otpService.verifyOtp(secret, otp) invokes the OTP verification logic implemented in the OtpService class. This service internally validates the provided OTP using the TOTP algorithm and determines whether the supplied code matches the expected value generated using the shared secret key and the current timestamp. If the OTP verification succeeds, the conditional block if (valid) executes and stores the session attribute MFA_VERIFIED with the value true using session.setAttribute("MFA_VERIFIED", true). This session attribute acts as a temporary authentication flag indicating that the current user session has successfully completed MFA verification. Subsequent requests processed by the custom MfaAuthenticationFilter can then inspect this session flag to determine whether the user is authorized to access protected resources. After storing the MFA verification status, the method returns the response string "MFA verification successful", informing the client that the second authentication factor has been successfully validated. If the OTP validation fails, the method skips the success block and returns the message "Invalid OTP", indicating unsuccessful MFA verification. The controller also defines another endpoint using @GetMapping("/secure/dashboard"), which exposes a protected API accessible only to authenticated and MFA-verified users. The method simply returns the response "Welcome to secure dashboard", demonstrating how secured resources can be protected behind MFA validation. In enterprise-grade applications, this controller can be enhanced further by adding features such as JWT token generation after successful MFA verification, OTP expiration handling, failed-attempt counters, rate limiting, account lockout mechanisms, encrypted session handling, audit logging, recovery codes, QR code provisioning for authenticator setup, and integration with external identity providers or centralized authentication systems.
2.7 application.properties
spring.application.name=spring-security-mfa server.port=8080 logging.level.org.springframework.security=DEBUG
The application.properties file is the central external configuration file used by Spring Boot applications to define runtime settings, application behavior, server configuration, logging rules, and environment-specific properties. The property spring.application.name=spring-security-mfa defines the logical name of the Spring Boot application. This application name is commonly used in logging systems, monitoring dashboards, distributed tracing tools, microservice discovery platforms, and centralized configuration management systems to uniquely identify the service instance within an enterprise environment. The property server.port=8080 configures the embedded web server, typically Apache Tomcat in Spring Boot applications, to run on port 8080. This means the application will be accessible using URLs such as http://localhost:8080. If this property is omitted, Spring Boot still defaults to port 8080, but explicitly defining it improves clarity and environment consistency. The property logging.level.org.springframework.security=DEBUG enables detailed debug-level logging specifically for the Spring Security framework. This configuration is extremely useful during development and troubleshooting because it allows developers to inspect internal Spring Security operations such as authentication processing, authorization decisions, filter chain execution, session creation, access-denied handling, login attempts, role validation, and MFA filter execution. When debug logging is enabled, developers can trace the complete security workflow and quickly identify issues related to endpoint protection, OTP validation, filter ordering, session management, or role-based access control. In production systems, however, DEBUG logging should generally be disabled or reduced to INFO or WARN levels to avoid excessive log generation and exposure of sensitive security information. Overall, this configuration file provides essential runtime settings required for running and debugging the Spring Security 7 Multi-Factor Authentication application effectively.
2.8 Running the Application
mvn spring-boot:run
The command mvn spring-boot:run is used to start and execute the Spring Boot application directly using Apache Maven without requiring manual packaging or deployment steps. Maven is a popular build automation and dependency management tool used extensively in Java and Spring Boot projects. The spring-boot:run goal is provided by the Spring Boot Maven Plugin and is specifically designed to simplify local application execution during development. When this command is executed from the root directory of the project containing the pom.xml file, Maven first resolves and downloads all required dependencies defined in the project configuration, including Spring Boot, Spring Security, and the Google Authenticator library used for TOTP-based MFA authentication. After dependency resolution, Maven compiles the source code, processes application resources, initializes the Spring Boot runtime environment, and starts the embedded servlet container such as Apache Tomcat. Once the application successfully starts, the embedded server begins listening on the configured port, which in this case is port 8080 as defined in the application.properties file. Developers can then access application endpoints using URLs such as http://localhost:8080/login, http://localhost:8080/verify-otp, and http://localhost:8080/secure/dashboard. During application startup, Spring Boot automatically scans the project for annotated components such as @Configuration, @Service, @RestController, and @Component classes, creates the required beans, initializes the Spring Security filter chain, configures authentication providers, and registers the custom MFA authentication filter. Because Spring Security debug logging is enabled, the console output will also display detailed security initialization logs, authentication processing details, filter execution sequences, and authorization decisions, which helps developers understand the complete internal workflow of the Multi-Factor Authentication system. This command is primarily used in development and testing environments because it allows rapid application startup and hot-reload style iterative development without requiring external application servers or complex deployment procedures.
2.9 Authentication Flow Output
The following authentication flow demonstrates the complete Multi-Factor Authentication lifecycle in a Spring Security 7 application. The workflow begins with traditional username-password authentication and then proceeds to OTP-based second-factor verification before granting access to protected resources. This layered authentication mechanism ensures that even if primary credentials are compromised, unauthorized users still cannot access secured endpoints without possessing the second authentication factor. The flow also demonstrates how Spring Security internally processes authentication requests, validates user sessions, invokes the custom MFA filter, and authorizes access based on successful OTP verification.
2.9.1 User Login Request
In the first step, the client submits login credentials to the Spring Security authentication endpoint. Spring Security internally processes the request using the UsernamePasswordAuthenticationFilter, validates the supplied credentials using the configured UserDetailsService, creates an authenticated session upon successful login, and stores authentication details inside the Spring Security context. At this stage, the user is authenticated with username and password only, but MFA verification has not yet been completed.
POST http://localhost:8080/login username=user password=password
After successful authentication, Spring Security redirects the user to the configured MFA verification flow endpoint. The user session is established, but access to sensitive APIs is still restricted until OTP validation succeeds.
2.9.2 Access Protected Secure Endpoint Before MFA
After login, the client attempts to access a protected API endpoint before completing OTP verification. The request reaches the custom MfaAuthenticationFilter, which checks whether the session contains the MFA_VERIFIED attribute. Since OTP verification has not yet occurred, the filter blocks access and returns an HTTP 403 Forbidden response. This demonstrates how MFA acts as an additional authorization layer after successful primary authentication.
GET http://localhost:8080/secure/dashboard -- Response HTTP Status: 403 Forbidden Response Body: MFA verification required
Although the user has successfully authenticated using username and password, the custom MFA filter enforces additional verification rules before granting access to sensitive resources. The filter validates whether the user session contains a successful MFA verification flag. Since the flag does not exist yet, Spring Security treats the request as partially authenticated and denies access. This mechanism significantly improves security because compromised passwords alone are insufficient to gain access to protected APIs.
2.9.3 OTP Verification Request
The client now submits the One-Time Password generated by an authenticator application such as Google Authenticator or Microsoft Authenticator. The OTP verification endpoint invokes the OtpService, which validates the supplied OTP using the shared secret key and the TOTP algorithm defined by RFC 6238. If validation succeeds, the application stores the MFA_VERIFIED session attribute indicating that the user has successfully completed second-factor authentication.
POST http://localhost:8080/verify-otp -- Request Body secret=ABC123XYZ otp=123456 -- Response HTTP Status: 200 OK Response Body: MFA verification successful
The OTP is generated using a Time-Based One-Time Password algorithm where both the client authenticator application and the server share a common secret key. The algorithm combines the secret key with the current timestamp to generate short-lived authentication codes that automatically expire after a predefined interval, typically 30 seconds. Because the OTP continuously changes and requires possession of the registered authenticator device, MFA provides strong protection against credential theft, replay attacks, brute-force attacks, and phishing attempts.
2.9.4 Access Protected Endpoint After MFA Verification
Once OTP verification succeeds, the client retries access to the protected endpoint. This time, the custom MFA filter detects the MFA_VERIFIED session attribute and allows the request to continue through the Spring Security filter chain to the controller endpoint. The user is now considered fully authenticated and authorized to access secured application resources.
GET http://localhost:8080/secure/dashboard -- Response: HTTP Status: 200 OK Response Body: Welcome to secure dashboard
After successful MFA verification, the application session contains both primary authentication status and second-factor verification status. During request processing, the MfaAuthenticationFilter validates the session attribute and determines that the user has successfully completed all required authentication stages. Spring Security therefore authorizes the request and forwards execution to the protected controller endpoint. This layered authentication approach ensures that sensitive APIs remain protected even when primary credentials are compromised because attackers would still require possession of the user’s registered authenticator device.
3. Conclusion
Multi-Factor Authentication in Spring Security 7 significantly improves application security by introducing additional verification layers beyond traditional username and password authentication mechanisms. Instead of relying solely on credentials that may be compromised through phishing attacks, credential leaks, or brute-force attempts, Spring Security 7 enables applications to enforce stronger identity validation using One-Time Passwords, authenticator applications, hardware tokens, biometric verification, or adaptive authentication strategies. Spring Security 7 provides highly flexible and extensible authentication mechanisms that support global MFA policies for all users, endpoint-specific MFA enforcement for sensitive APIs, time-based authentication rules for high-risk access periods, user-based MFA policies for privileged accounts such as administrators, and adaptive risk-based authentication models that dynamically evaluate login behavior, device trust, geolocation, and suspicious activity patterns. By combining Spring Security filters, OTP verification services, session-based authentication handling, custom authorization logic, and advanced security configurations, developers can design and implement secure enterprise-grade authentication systems with minimal complexity while maintaining scalability, flexibility, and strong protection against unauthorized access and modern security threats.




