Summary / Expected Behavior When using @EnableGlobalMultiFactorAuthentication with Spring Security, the framework currently defaults the authentication entry point for missing factors to the one used for general exception handling (e.g., ExceptionTranslationFilter's default AuthenticationEntryPoint).

I request the ability to customize this default authentication entry point for MFA flows, similar to how the accessDeniedHandler can be customized. This would allow developers to dynamically determine the login mechanism (e.g., redirect URL) based on the current request context before the initial authentication process begins.

Detailed Use Case I am implementing an OAuth 2.0 Authorization Code Flow where the desired initial login experience is conditional.

If the authorization request includes a specific parameter, e.g., login-mode=ott, the user should be redirected to a One-Time Token (OTT) login page.

Otherwise, the user should be redirected to the standard login form.

I attempted to use a SecurityMatcher combined with hasRole() (for the required factor) and a custom LoginUrlAuthenticationEntryPoint to handle this logic. However, this approach fails because:

The MFA factor check (for PASSWORD/OTT factors) does not result in an AccessDeniedException.

Consequently, the attribute WebAttributes.REQUIRED_FACTOR_ERRORS is not set, and the configured LoginUrlAuthenticationEntryPoint logic is bypassed, as it's not treated as an access-denied scenario that would trigger the missing factor handling.

The current design prevents a clean way to apply request-specific authentication entry point logic (like parameter checking) before the factor completion process begins.

Current Behavior When configuring Spring Security with @EnableGlobalMultiFactorAuthentication and two factors (e.g., PASSWORD/OTT), the application:

By default, uses a single LoginUrlAuthenticationEntryPoint as the factor completion mechanism.

After initial login/authentication, it proposes using a DelegatingMissingAuthorityAccessDeniedHandler for subsequent factor checks.

This singular entry point is restrictive and lacks the necessary customization hook for dynamic flow selection.

Context Spring Security Version: 7.0.0-SNAPSHOT (or latest stable version if applicable)

Workarounds (and why they are suboptimal) A custom AuthenticationEntryPoint could be created that checks for the login-mode request parameter. However, this is less elegant and less declarative than leveraging a dedicated security matcher/hook tailored for default MFA entry points, which would align with the existing pattern for customizing accessDeniedHandler.

Comment From: joaquinjsb

Also, I believe that there's maybe ground for a bug here ? seems like it's not firing the multifactor authentication for OAuth2 authorization? I got missleaded by chrome trying to go to http://127.0.0.1:8080/.well-known/appspecific/com.chrome.devtools.json?continue, which them prompts the ott login, but with a clean session, there's no prompt for mfa when doing OAuth2 authorization code flow for example.

Comment From: jzheaux

Hi, @joaquinjsb, AuthenticationEntryPoints are for commencing authentication, and so there is no MFA context at that point. That is, there is no access denied because no authentication has happened yet. Any AccessDeniedException is adapted to an AuthenticationException pre-authentication.

Have you already tried configuring the AuthenticationEntryPoint directly on exceptionHandling? I'd imagine an entry point similar to this:

@Component
class LoginModeAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException ex) {
        if ("ott".equals(request.getParameter("login-mode")) {
            response.sendRedirect("/my/ott/page");
            return;
        }
        response.sendRedirect("/my/form/page");
    }
}

And then:

http
    // ...
    .exceptionHandling((exceptions) -> exceptions
        .authenticationEntryPoint(loginModeAuthenticationEntryPoint)
    )
    // ...

Comment From: jzheaux

Will you please log a separate issue for the Chrome devtools behavior you described? Please include a description of how to reproduce and a sample application.

Comment From: joaquinjsb

Hello, yes I've applied your suggested approach( as I wrote on the workaround), I guess I'll follow that path, thanks!

I will open the dev tools issue.

thanks