Body: I developed an IAM system using Spring Boot where I integrated multi-factor authentication (MFA).
Here’s the flow I implemented:
A client sends a GET request to /oauth2/authorize.
This redirects to /login.
If the user enters the correct credentials, an OTP is sent via email.
After OTP verification, the system should generate the auth code.
To achieve this, I’m caching the /oauth2/authorize request until the OTP is verified. After verification, I recall the cached request and generate the auth code.
The issue:
On my local machine, everything works fine.
On the server, the cached request becomes null after OTP verification.
So it seems like the /oauth2/authorize request is not being cached properly on the server environment.
Question: Why is my cached request becoming null on the server but working fine locally? How can I fix this issue so the /oauth2/authorize request is retained until OTP verification is complete?
Notes:
Using Spring Boot (OAuth2 Authorization Server + custom MFA).
Session management and caching are being used to store the request.
No errors in logs, just null when trying to retrieve the cached request.
@Configuration
public class DefaultSecurityConfig {
private final CorsConfigurationSource corsConfigurationSource;
private final UserDetailsService userDetailsService;
private final PasswordEncoderFactory passwordEncoderFactory;
private static final Logger LOGGER = LogManager.getLogger(DefaultSecurityConfig.class);
private final MFASettings mfaSettings;
public DefaultSecurityConfig(CorsConfigurationSource corsConfigurationSource,
UserDetailsService userDetailsService,
PasswordEncoderFactory passwordEncoderFactory, final MFASettings mfaSettings) {
this.corsConfigurationSource = corsConfigurationSource;
this.userDetailsService = userDetailsService;
this.passwordEncoderFactory = passwordEncoderFactory;
this.mfaSettings = mfaSettings;
}
@Bean
@Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http, JwtDecoder jwtDecoder) throws Exception {
http
.cors(cors -> cors.configurationSource(corsConfigurationSource))
.csrf(crs -> crs.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/oauth2/token").permitAll()
.requestMatchers("/.well-known/jwks.json").permitAll()
.requestMatchers("/swagger-ui/**",
"/v3/api-docs/**",
"/swagger-ui.html",
"/webjars/**"
).permitAll()
.anyRequest().authenticated())
.userDetailsService(userDetailsService)
.formLogin(form -> form
.loginPage("/login") // Use custom login page
.failureUrl("/login?error=true")
.successHandler(mfaAuthenticationSuccessHandler()) // hook MFA check here
.permitAll()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt.decoder(jwtDecoder)));
return http.build();
}
@Bean
public AuthenticationSuccessHandler mfaAuthenticationSuccessHandler() {
return (request, response, authentication) -> {
// Check if MFA is required
LOGGER.info("is Multi-Factor authentication enabled : {}", mfaSettings.getMFAEnable());
if (mfaSettings.getMFAEnable() && authentication.getAuthorities().stream()
.noneMatch(a -> a.getAuthority().equals("MFA_VERIFIED"))) {
// Generate 6-digit code
String code = RandomStringUtils.randomNumeric(6);
// Send email (get MfaController bean)
WebApplicationContext ctx =
WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());
MfaController mfaController = ctx.getBean(MfaController.class);
LoginUser user = (LoginUser) authentication.getPrincipal();
if(mfaSettings.getEmailEnable()) {
mfaController.sendEmail(user.getEmail(), code);
}
if(mfaSettings.getMFAEnable()) {
// TODO: have to implement
}
request.getSession().setAttribute("MFA_CODE", code);// Store in session
request.getSession().setAttribute("MFA_TIMESTAMP", System.currentTimeMillis());
LOGGER.info("SuccessHandler session ID: {} code {}", request.getSession().getId(), code);
// Save the original request (/oauth2/authorize with params)
RequestCache requestCache = new HttpSessionRequestCache();
SavedRequest savedRequest = requestCache.getRequest(request, response);
if (savedRequest == null || !savedRequest.getRedirectUrl().contains("/oauth2/authorize")) {
// Save the original request if not already saved or incorrect
requestCache.saveRequest(request, response);
}
// Redirect to MFA verify
response.sendRedirect("/mfa/verify");
}
else {
// Proceed with default redirect (to saved request, e.g., /oauth2/authorize)
RequestCache requestCache = new HttpSessionRequestCache();
SavedRequest savedRequest = requestCache.getRequest(request, response);
LOGGER.info("Saved Request URL (no MFA): {}", savedRequest != null ? savedRequest.getRedirectUrl() : "null");
new SavedRequestAwareAuthenticationSuccessHandler().onAuthenticationSuccess(request, response, authentication);
}
};
}
}
I want to properly cache that /oauth2/authorize request with all the details
I am using this script to run application
nohup java/bin/java -Xmx2G -Xms2G -Xss256M -XX:MetaspaceSize=300m -XX:MaxMetaspaceSize=300m -XX:+UseG1GC -server -jar ./code-0.0.1-SNAPSHOT.jar --mode.server > IAMNohup &