Skip to content

replace wp-login.php to wp_login_url#886

Open
masteradhoc wants to merge 4 commits into
WordPress:masterfrom
masteradhoc:885-wpml-fix
Open

replace wp-login.php to wp_login_url#886
masteradhoc wants to merge 4 commits into
WordPress:masterfrom
masteradhoc:885-wpml-fix

Conversation

@masteradhoc
Copy link
Copy Markdown
Collaborator

What?

Fixes #885

Rewrites login_url() to delegate to WordPress's native wp_login_url() instead of constructing URLs directly from site_url( 'wp-login.php' ). Also updates the redirect_to fallback in show_two_factor_login() to go through the login_redirect filter instead of a hardcoded admin_url().

Why?

On WPML multi-domain setups (e.g. site.com, en.site.com, fr.site.com), users on 0.15.0+ experience either "ERROR: Invalid verification code" on correct TOTP codes, or a session loop where every click after login logs them out again. Downgrading to 0.14.2 resolves both symptoms.

The root cause is that login_url() calls site_url( 'wp-login.php' ) directly. WPML (and Polylang, TranslatePress) hook WordPress's login_url filter to rewrite login URLs per language/domain — but that filter only fires inside wp_login_url(), which this plugin never calls. As a result, the 2FA form action and all redirect URLs point to the wrong domain, breaking nonce validation and session continuity.

Reference: https://wordpress.org/support/topic/two-factor-0-14-2-works-0-15-0-doesnt/

How?

// login_url() — before
$url = site_url( 'wp-login.php?action=' . $params['action'], $scheme );

// login_url() — after
$url = set_url_scheme( wp_login_url( '', false ), $scheme );
if ( $action ) { $url = add_query_arg( 'action', $action, $url ); }

// show_two_factor_login() — before
$redirect_to = isset( $_REQUEST['redirect_to'] ) ? $_REQUEST['redirect_to'] : admin_url();

// show_two_factor_login() — after
$redirect_to = isset( $_REQUEST['redirect_to'] )
    ? wp_unslash( $_REQUEST['redirect_to'] )
    : apply_filters( 'login_redirect', admin_url(), '', null );

No behaviour change on standard installs. WPML, Polylang, TranslatePress, and multisite mapped domains all benefit automatically.

Use of AI Tools

AI assistance: Yes
Tool(s): Claude (Anthropic)
Model(s): Claude Sonnet 4.6
Used for: Identifying root cause, drafting the code change. Final implementation reviewed and tested by me.

Testing Instructions

Standard single-site (regression check)

  1. Enable TOTP for a test user on a plain single-site install
  2. Log out and log back in
  3. Confirm 2FA challenge appears and TOTP code is accepted as before

WPML multi-domain

  1. Install WPML, configure two languages on separate subdomains (e.g. en.site.com,
    fr.site.com)
  2. Enable TOTP for a test user
  3. Log in from en.site.com — confirm the 2FA form action URL stays on en.site.com
  4. Enter correct TOTP code — confirm successful login with no immediate logout
  5. Repeat from fr.site.com

Backup provider links

  1. Enable both TOTP and Email as providers for a test user
  2. On the 2FA challenge screen confirm the "Try another method" links use the correct
    domain/language URL

Revalidation

  1. After login, trigger a revalidation prompt (revalidate_2fa)
  2. Confirm the revalidation URL is on the correct domain

Screenshots or screencast

No UI changes — not applicable.

Changelog Entry

Fixed - login_url() now delegates to wp_login_url() so the WordPress login_url filter is respected, fixing session loops and invalid verification code errors on WPML and other multi-domain setups.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes multi-domain/multilingual login issues (WPML/Polylang/TranslatePress, mapped domains) by updating Two-Factor’s login URL generation to use WordPress core’s wp_login_url() (so the core login_url filter can rewrite domains correctly) and by routing the default redirect_to through login_redirect.

Changes:

  • Update Two_Factor_Core::login_url() to build URLs starting from wp_login_url() and then apply the requested scheme/query args.
  • Update show_two_factor_login() so the fallback redirect target is derived via the login_redirect filter rather than hardcoded admin_url().

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread class-two-factor-core.php Outdated
Comment thread class-two-factor-core.php Outdated
Comment thread class-two-factor-core.php Outdated
masteradhoc and others added 2 commits May 4, 2026 22:28
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

class-two-factor-core.php:1274

  • The new behavior of delegating Two_Factor_Core::login_url() to wp_login_url() is important for multilingual/multi-domain setups, but there’s no unit test asserting that the WordPress login_url filter is actually respected (e.g., by adding a temporary login_url filter and confirming the returned URL includes the filtered domain/args). Adding a test would help prevent regressions back to manually constructed URLs.
		// Use WordPress's own wp_login_url() so that plugins like WPML which
		// hook the `login_url` filter can translate or rewrite the URL correctly.
		// wp_login_url() applies the `login_url` filter, giving multilingual
		// plugins a chance to redirect to a translated login page. Any requested
		// scheme is applied below with set_url_scheme().
		$action = isset( $params['action'] ) ? $params['action'] : '';
		$url    = wp_login_url( '', false );

		// Re-apply the scheme since wp_login_url() doesn't expose it as a parameter.
		$url = set_url_scheme( $url, $scheme );

		// Compat: WordPress core passes action directly in the path for certain actions
		// (e.g. wp-login.php?action=validate_2fa). Preserve that behaviour.
		if ( $action ) {
			$url = add_query_arg( 'action', $action, $url );
		}

		if ( $params ) {
			$url = add_query_arg( $params, $url );
		}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread class-two-factor-core.php
$redirect_to = isset( $_REQUEST['redirect_to'] ) ? $_REQUEST['redirect_to'] : admin_url();
$redirect_to = isset( $_REQUEST['redirect_to'] )
? wp_unslash( $_REQUEST['redirect_to'] )
: apply_filters( 'login_redirect', admin_url(), '', $user );
Comment thread class-two-factor-core.php Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@masteradhoc masteradhoc marked this pull request as ready for review May 4, 2026 20:37
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 4, 2026

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: 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.

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