Skip to content

Use wp_login_url() for login URLs and defer the redirect decision to core logic - #1000

Merged
kasparsd merged 25 commits into
masterfrom
fix-use-login-url
Sep 27, 2026
Merged

kasparsd merged 25 commits into
masterfrom
fix-use-login-url

Conversation

@kasparsd

@kasparsd kasparsd commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator

What?

This PR completes the switch of Two_Factor_Core::login_url() to WordPress's native wp_login_url() and reworks how the post-2FA redirect_to value flows through the login, validation, and revalidation steps.

Closes #886 as superseded.

Why?

Fixes #885.

PR #886 ("replace wp-login.php to wp_login_url") fixes an important multilingual bug: since 0.15.0, multilingual plugins (WPML, Polylang, TranslatePress) that hook the login_url filter were never applied, sending 2FA form actions and redirects to the wrong domain on multi-language setups. This PR supersedes it because it takes the same core idea further and addresses the review concerns raised there:

  • replace wp-login.php to wp_login_url #886's added apply_filters( 'login_redirect', admin_url(), '', null ) fallback in show_two_factor_login() passed an empty $requested_redirect_to and a null user to a filter that expects a WP_User, applied the filter too early (before validation), and conflicted with the second login_redirect application in the validation step. Instead, the redirect_to default is now simply empty at the render step (show_two_factor_login(), login_form_revalidate_2fa()), and core's decision-making happens once, at the very last step before wp_safe_redirect().
  • The login_redirect filter is applied exactly once per flow, with the actual requested redirect_to and a real WP_User.
  • replace wp-login.php to wp_login_url #886 did not add tests for the param-to-URL behavior, which was also requested in review.

How?

  • login_url() now builds on wp_login_url( '', false ) + set_url_scheme(), so the login_url filter (and the multilingual plugins hooking it) affects all generated URLs. Parameters — including redirect_to — are added via add_query_arg() only when passed.
  • show_two_factor_login() and login_form_revalidate_2fa() no longer default redirect_to to admin_url(); the value stays empty unless it was actually requested or provided by the login_redirect filter, letting core decide.
  • New private helper get_login_redirect_fallback() centralizes the final decision: it applies the login_redirect filter once, and when the destination is still empty it mirrors the capability-based fallback from the case 'login' block in wp-login.php (multisite dashboard destinations, profile.php for users without edit_posts, front end for users without read), so 2FA logins land in the same place a regular login would.
  • Tests: a data-provider suite covering param → exact URL pairs for login_url() (including redirect_to being added when present and absent otherwise), the redirect decision table (role capabilities, filter overrides, empty fallback), and end-to-end redirect tests for both the validation and revalidation flows.

Use of AI Tools

AI assistance: Yes
Tool(s): Pi (coding agent)
Model(s): GLM
Used for: test scaffolding and data providers; implementation and tests reviewed and edited by me.

Testing Instructions

  1. npm run env start followed by npm test — all tests should pass.
  2. On a multilingual setup (WPML or Polylang with per-language domains), log in as a 2FA-enabled user and verify the 2FA form posts and redirects to the correct language domain.
  3. Log in without a redirect_to parameter as (a) an editor and (b) a subscriber, and verify you are redirected to the admin dashboard and your profile page respectively, mirroring a non-2FA login.
  4. Hook login_redirect to return a custom URL and verify it is respected after 2FA.

Changelog Entry

Fixed - Respect the login_url filter for the two-factor login form destinations, fixing two-factor flows on multilingual sites that redirect logins to a different domain, and align the post-login redirect decision with the one used by wp-login.php.

Open WordPress Playground Preview

@github-actions

github-actions Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: kasparsd <kasparsd@git.wordpress.org>
Co-authored-by: masteradhoc <masteradhoc@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

Comment thread class-two-factor-core.php
}

$redirect_to = isset( $_REQUEST['redirect_to'] ) ? esc_url_raw( wp_unslash( $_REQUEST['redirect_to'] ) ) : admin_url(); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Value only used for redirect; auth protected by 2FA login nonce later.
$redirect_to = isset( $_REQUEST['redirect_to'] ) ? esc_url_raw( wp_unslash( $_REQUEST['redirect_to'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Value only used for redirect; auth protected by 2FA login nonce later.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

delay the redirect decision to the very late, if nothing custom is provided here.

Comment thread class-two-factor-core.php Outdated
Comment thread tests/providers/class-two-factor-backup-codes-rest-api.php
@kasparsd
kasparsd merged commit b80ef95 into master Sep 27, 2026
25 checks passed
@kasparsd
kasparsd deleted the fix-use-login-url branch September 27, 2026 06:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: login_url() breaks WPML / multi-domain setups — causes session loops and "Invalid verification code"

2 participants