Problem/Motivation

When Email TFA is enabled, a successful login should redirect anonymous users to the verification route (email_tfa.email_tfa_verify_login). On sites that add custom #submit handlers to user_login_form via hook_form_alter(), those handlers may run after EmailTfaLoginForm::submitForm() and overwrite the redirect (e.g. with setRedirectUrl() to or another route).

Steps to reproduce

1. Enable Email TFA (status: true, e.g. tracks: globally_enabled).
2. Add a custom submit handler to the login form:

function example_form_user_login_form_alter(array &$form, FormStateInterface $form_state) {
  $form['#submit'][] = 'example_user_login_redirect_submit';
}
function example_user_login_redirect_submit(array &$form, FormStateInterface $form_state) {
  $form_state->setRedirectUrl(Url::fromRoute('<front>'));
}

3. Log in as a user subject to Email TFA (e.g. via the User login block on the front page).
4. Observe the HTTP response after form submission.

Expected behavior

The user is redirected to /tfa/verify/{uid}/{hash} to enter the one-time code.

Actual behavior

The user is redirected to the URL set by the custom submit handler (e.g. the front page) instead of the TFA verification page. The TFA tempstore data may still be written, but the verification form is never shown.

Root cause

Drupal runs all #submit handlers in order. EmailTfaLoginForm::submitForm() sets the redirect to the verify route, but later handlers can call setRedirectUrl() / setRedirect() again and replace it.

This is easy to miss because:
- The TFA login flow appears to work in debugging (tempstore entries are created).
- EmailTfaVerifyLoginForm::buildForm() is never reached because the browser never requests the verify URL.
- The problem is not in EmailTfaVerifyLoginForm::access().

Proposed solutions

Any of the following would help:
1. In EmailTfaLoginForm::submitForm(), call $form_state->setIgnoreDestination(TRUE) before setting the TFA redirect (helps when destination query parameters are also involved).
2. Document that custom #submit handlers on user_login_form must not override redirects when getRedirect() already points to email_tfa.email_tfa_verify_login.
3. Use a later submit handler in the module (e.g. via hook_form_alter() with #submit appended after other modules) to restore the TFA redirect if it was overwritten — fragile but defensive.
4. Recommended pattern for integrators (can be added to README):

function mymodule_user_login_redirect_submit(array &$form, FormStateInterface $form_state) {
  $redirect = $form_state->getRedirect();
  if ($redirect instanceof Url && $redirect->getRouteName() === 'email_tfa.email_tfa_verify_login') {
    return;
  }
  $form_state->setRedirectUrl(Url::fromRoute('<front>'));
}

Environment

Drupal 10/11
email_tfa (version as applicable)
Login via User login block posting to (e.g. front page with ?destination=/)

Comments

cola created an issue.

mupsi’s picture

Thank you Cola for documenting this, I got this exact issue on an existing codebase I didn't know very well yet and where I added this module. Found a custom redirect that I was able to update so that it doesn't interfere with the module.

Makes complete sense as this is a recurring issue with the order of hooks execution, but I didn't understand the issue right away.