Multi-Factor Authentication with Spring Boot 2 and Spring Security 5
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.
Introduction
Multi-factor authentication in Spring Boot 2 is usually built as a two-step login flow: password first, then a second factor such as a time-based one-time password. Spring Security 5 gives you the authentication hooks, but you still need to decide how to represent the intermediate state between "password accepted" and "fully authenticated."
Model MFA as Two Distinct Steps
A clean mental model is:
- verify username and password
- if the user has MFA enabled, require an OTP before granting full access
That means a successful password check does not always mean a complete login. Instead, it may mean "first factor passed, continue to OTP verification."
A simple user entity might look like this:
The important fields are the MFA flag and the TOTP secret used to validate the second factor.
Authenticate the Password First
Your normal UserDetailsService and password encoder still handle the first factor. The difference is what happens after the password is correct.
In a real application you would load the user from a database. The key design choice is whether the authentication success handler sends the user directly to the app or redirects to an MFA screen.
Redirect to an MFA Verification Step
A common pattern is to store a temporary marker in the session after password login, then redirect to /mfa.
This is easier to reason about than trying to overload one authentication object with both incomplete and complete states.
Verify the TOTP Code
For the second factor, many Spring applications use a TOTP library such as Google Authenticator compatible code generation. The service interface can stay simple.
Then a controller can complete the MFA flow:
This example is intentionally simplified, but the flow is the important part: pending state first, verified state second.
Configure Spring Security Around the Flow
Your security configuration should allow access to the login page and MFA page, but require a completed login for protected resources.
In a production system, you would also add logic that blocks access to the rest of the application when MFA is still pending.
Do Not Forget Recovery and Secret Storage
A working OTP screen is only the start. A real MFA implementation also needs:
- encrypted storage for the TOTP secret
- enrollment flow with QR code provisioning
- backup or recovery codes
- rate limiting on OTP attempts
- audit logging for second-factor failures
Without recovery handling, MFA turns into a support problem as soon as users lose their authenticator app or replace a phone.
Common Pitfalls
The biggest mistake is treating password success as full authentication even for users who should still pass a second factor. Another common issue is storing the TOTP secret in plain text or handling OTP verification without rate limiting. Developers also often forget recovery flows, which makes MFA impossible to support operationally. Finally, the intermediate "MFA pending" state must be handled carefully so partially authenticated users cannot reach protected endpoints.
Summary
- A practical Spring Boot 2 and Spring Security 5 MFA flow is usually password first, OTP second.
- Model the post-password state as "MFA pending" rather than fully authenticated.
- Verify the TOTP code in a dedicated step such as
/mfa. - Securely store secrets and plan for recovery, rate limiting, and auditing.
- The security challenge is not only generating OTPs, but also correctly representing incomplete versus complete authentication.
Related reading
- Multiple WebSecurityConfigurerAdapter in spring boot for multiple patterns
- Mutating Webhook does not invoke endpoint because certificate signed by unknown authority
- MySQL 8.0 - Client does not support authentication protocol requested by server; consider upgrading MySQL client
- Mysql adding user for remote access
- Multi-project test dependencies with gradle
- Multipart File upload Spring Boot
- MySQL ERROR 1045 28000 Access denied for user 'bill''localhost' using password YES
- MySQL parameterized queries

System Design Fundamentals
Build a strong foundation in designing scalable, reliable distributed systems.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.